Итак, мы столкнулись с провалом теста в Linux, который, как я полагаю, связан с неправильными предположениями с моей стороны относительно действительности указателей, относящихся к встроенным литералам. Код похож на этот псевдокод:
auto obj = func( 'c', "str" ); // (1)
big_type big_object; // (2)
В (1) func() возвращает объект, который хранит указатель const на символьный литерал и один на строковый литерал. Проверка в отладчике показывает, что оба верны.
В (2) отладка показывает, что то, что раньше было 'c' в памяти, на которую ссылается const char* в obj, перезаписывается.
Тец показывает, что то же самое происходит и с литералами int и double. Это происходит в GCC 5.4.1, этого не происходит в GCC 4.1.2.
Проработав C++ более 25 лет, я научился предполагать, что обычно компилятор прав, а я ошибаюсь; так что я тоже делаю это здесь.
Однако, хотя я знаю, что если это касается только литералов небольших встроенных типов, я могу исправить это (скопировав их вместо ссылки на них), если это могло произойти и с объектами произвольного размера ("str"), мы есть довольно большая проблема.
Так может кто-нибудь объяснить точные правила относительно этого?
Если вы хотите подтвердить это, просто измените свою функцию, чтобы взять const char *, который вы хотите сохранить, и попробуйте передать &'c' в качестве аргумента. Что теперь говорит компилятор?
@Some Предположим, что это функция foo func(const char&, const char*).
@Useless Я не думаю, что могу разгребать адрес символьного литерала, потому что это rvalue. (Обратите внимание, что я могу взять адрес строкового литерала.)





Строковые литералы сохраняются на протяжении всего жизненного цикла программы. Символьные литералы этого не делают, потому что они на самом деле являются просто целыми числами и не получают никакой особой обработки, присущей строкам.
Чтобы взглянуть на это с другой стороны: ваша функция получает два аргумента: один - символьное значение, а другой - указатель на строковый литерал. Создание копии указателя на строку - это нормально, но создание указателя на значение, которое было передано в качестве аргумента, недопустимо. Так же, как если бы вы создали указатель на указатель на строку, у вас были бы проблемы. Аргументы функции уничтожаются по завершении вызова функции. В случае символа это означает, что символ исчез, тогда как в случае указателя на строку вы сделали копию и сохранили ее.
От [expr.prim.literal§1]:
A literal is a primary expression. Its type depends on its form. A string literal is an lvalue; all other literals are prvalues.
Более подробную информацию об этих lvalue можно найти на [lex.string§16]:
Evaluating a string-literal results in a string literal object with static storage duration, initialized from the given characters as specified above. [...]
Что напрямую решает проблему: строковые литералы - единственные литералы, которые имеют статическую продолжительность хранения, и поэтому на них могут ссылаться указатели, которые переживают выражение, в котором они появляются.
Да, это ключевой момент, который мы также обнаружили, обсуждая это за обедом. Более того, проблема не только в литералах или встроенных модулях, это произойдет с любым rvalue, например std::string("temp"). К счастью, это проблема только в текущем тестовом коде, поскольку в производственной среде результат вызова func() всегда оценивается до конца полного выражения. Таким образом, решение, похоже, состоит в том, чтобы изменить тесты и добавить строгое предупреждение для func(), что его результат непостоянен. Поскольку ваш ответ является первым ответом на соответствующие стихи стандарта, я приму его.
Предполагая, что func определяется примерно так:
some_class_type func(const char& ch, const char* str)
{
some_class_type some_object;
some_object.pch = &ch;
some_object.pstr = str;
return some_object;
}
Затем вы сохраняете указатель на временную переменную с помощью &ch.
Время жизни ch будет нет полной программой, только до конца полного выражения (то есть вызова func('c', "str")), тогда временная переменная перестанет существовать, и вы останетесь с ошибочным указателем.
Для одиночных символов, таких как одиночные целые числа или значения с плавающей запятой, почти никогда не нужно использовать указатели на них. Храните значения.
Для точных правил, вероятно, достаточно (хотя стандартная цитата Квентина, очевидно, более авторитетна) указать следующее предложение о строковые литералы
String literals have static storage duration, and thus exist in memory for the life of the program
которого нет ни в одном из другие типы буквального.
Другой способ взглянуть на это - пересмотреть свой код.
object func(char c, const char *s)
{
return object{&c, s};
}
и обратите внимание, что строковый литерал не передается по значению. Поскольку (как только он распадается на указатель) вы просто передаете адрес первого символа - этот массив должен оставаться действительным в течение хотя бы некоторого времени, и поскольку нет способа узнать, каким должно быть время жизни, статическая длительность это нормальное значение по умолчанию.
Вы говорите, что храните указатель на символ
'c'? Но в функции, которой не существует литерала, все, что у вас есть, - это переменная аргумента, и сохранение указателя на нее аналогично хранению указателя на локальную переменную (аргументы в основном являются локальными переменными). Без надлежащего MCVE будет очень сложно сказать вам что-нибудь еще.