Ссылаясь на литералы встроенного типа

Итак, мы столкнулись с провалом теста в 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"), мы есть довольно большая проблема.

Так может кто-нибудь объяснить точные правила относительно этого?

Вы говорите, что храните указатель на символ 'c'? Но в функции, которой не существует литерала, все, что у вас есть, - это переменная аргумента, и сохранение указателя на нее аналогично хранению указателя на локальную переменную (аргументы в основном являются локальными переменными). Без надлежащего MCVE будет очень сложно сказать вам что-нибудь еще.

Some programmer dude 03.09.2018 13:50

Если вы хотите подтвердить это, просто измените свою функцию, чтобы взять const char *, который вы хотите сохранить, и попробуйте передать &'c' в качестве аргумента. Что теперь говорит компилятор?

Useless 03.09.2018 13:51

@Some Предположим, что это функция foo func(const char&, const char*).

sbi 03.09.2018 13:51

@Useless Я не думаю, что могу разгребать адрес символьного литерала, потому что это rvalue. (Обратите внимание, что я могу взять адрес строкового литерала.)

sbi 03.09.2018 13:52
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
4
95
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Строковые литералы сохраняются на протяжении всего жизненного цикла программы. Символьные литералы этого не делают, потому что они на самом деле являются просто целыми числами и не получают никакой особой обработки, присущей строкам.

Чтобы взглянуть на это с другой стороны: ваша функция получает два аргумента: один - символьное значение, а другой - указатель на строковый литерал. Создание копии указателя на строку - это нормально, но создание указателя на значение, которое было передано в качестве аргумента, недопустимо. Так же, как если бы вы создали указатель на указатель на строку, у вас были бы проблемы. Аргументы функции уничтожаются по завершении вызова функции. В случае символа это означает, что символ исчез, тогда как в случае указателя на строку вы сделали копию и сохранили ее.

Ответ принят как подходящий

От [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(), что его результат непостоянен. Поскольку ваш ответ является первым ответом на соответствующие стихи стандарта, я приму его.

sbi 03.09.2018 15:57

Предполагая, что 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};
}

и обратите внимание, что строковый литерал не передается по значению. Поскольку (как только он распадается на указатель) вы просто передаете адрес первого символа - этот массив должен оставаться действительным в течение хотя бы некоторого времени, и поскольку нет способа узнать, каким должно быть время жизни, статическая длительность это нормальное значение по умолчанию.

Другие вопросы по теме