«Время жизни» строкового литерала в C


85

Разве указатель, возвращаемый следующей функцией, не будет недоступен?

Значит, время жизни локальной переменной в C / C ++ находится практически только внутри функции, верно? Это означает, что после char* foo(int)завершения указатель, который он возвращает, больше ничего не значит, верно?

Меня немного смущает время жизни локальной переменной. Что такое хорошее разъяснение?


10
Единственная переменная, которая есть в вашей функции, - это параметр int rc. Его время жизни заканчивается при каждой из return-х. Указатели, которые вы возвращаете, относятся к строковым литералам. Строковые литералы имеют статическую продолжительность хранения: их время жизни не меньше времени жизни программы.
Kaz

14
@PedroAlves Почему бы и нет? Методы допускают абстракцию; что, если в будущем строка будет считываться из ресурса перевода, но поддержка интернационализации не требуется для V1 (или V0.5) продукта?
dlev 09

1
@PedroAlves «Ваш код обязательно будет работать (и вы сможете увидеть это, если попытаетесь скомпилировать)» . Многие (большинство? Практически все?) Компиляторы c будут использовать недопустимый код и часто выдают код, который кажется работающим. Но попробуйте в другом компиляторе (или даже в другой версии того же компилятора), и он может упасть.
dmckee --- котенок экс-модератора

6
@PedroAlves, функция, которая возвращает одну постоянную строку, может иметь ограниченное использование, но как насчет функции, которая возвращает любую из нескольких постоянных строк в зависимости от ввода или состояния объекта? Простым примером может служить функция преобразования перечисления в его строковое представление.
Марк Рэнсом,

4
strerrorОчевидно, вы никогда не видели эту функцию.
Kaz

Ответы:


86

Да, время жизни локальной переменной находится в области ( {, }), в которой она создана.

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

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

Предупреждение!

Однако обратите внимание, что любая попытка изменить содержимое строкового литерала является неопределенным поведением (UB). Пользовательским программам не разрешается изменять содержимое строкового литерала.
Следовательно, всегда рекомендуется использовать constпри объявлении строкового литерала.

вместо,

Фактически, в C ++ не рекомендуется объявлять строковый литерал без символа, constхотя и не в C. Однако объявление строкового литерала с a constдает вам преимущество, заключающееся в том, что компиляторы обычно выдают предупреждение, если вы попытаетесь изменить строковый литерал в второй случай.

Пример программы :

Вывод:

cc1: предупреждения обрабатываются как ошибки
prog.c: В функции 'main':
prog.c: 9: error: передача аргумента 1 из 'strcpy' отбрасывает квалификаторы из целевого типа указателя

Обратите внимание, что компилятор предупреждает о втором случае, но не о первом.


Чтобы ответить на вопрос, который задают здесь несколько пользователей:

В чем дело с целочисленными литералами?

Другими словами, действителен ли следующий код?

Ответ: нет, этот код недействителен. Он неправильно сформирован и приведет к ошибке компилятора.

Что-то вроде:

Строковые литералы являются l-значениями, то есть: вы можете взять адрес строкового литерала, но не можете изменить его содержимое.
Тем не менее, любые другие литералов ( int, float, char, и т.д.) являются г-значения (стандарт С использует термин значение выражения для них) и их адреса не могут быть приняты на всех.


[Ссылка 1] Стандарт C99 6.4.5 / 5 «Строковые литералы - семантика»:

На этапе трансляции 7 к каждой многобайтовой символьной последовательности, полученной в результате строкового литерала или литералов, добавляется байт или код нулевого значения. Последовательность многобайтовых символов затем используется для инициализации массива статической продолжительности хранения и длины, достаточной для хранения последовательности . Для литералов символьной строки элементы массива имеют тип char и инициализируются отдельными байтами многобайтовой последовательности символов; для широких строковых литералов элементы массива имеют тип wchar_t и инициализируются последовательностью широких символов ...

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


Что, если пользователь возвращает что-то вроде этого. char * a = & "abc"; вернуть; Это будет недействительно?
Эшвин

@Ashwin: Тип строкового литерала char (*)[4]. Это связано с тем, что тип «abc» равен, char[4]а указатель на массив из 4 символов объявлен как char (*)[4], Итак, если вам нужно взять его адрес, вам нужно сделать это как char (*a)[4] = &"abc";и Да, он действителен.
Alok Save

@Als "abc" есть char[4]. (Из-за '\0')
asaelr 02

1
Может быть , это также будет хорошая идея , чтобы предупредить , что char const s[] = "text";это не делает sбуквенный символ, и , следовательно , s будет уничтожено в конце области, поэтому все выжившие указатели на него будут свисать.
celtschk 02

1
@celtschk: Я бы с удовольствием, но Q касается именно строковых литералов, поэтому я бы остановился на обсуждаемой теме. Тем не менее, для интересующихся мой ответ здесь: в чем разница между char a [] = «string» и char * p = «строка»? должно быть довольно полезным.
Alok Save

74

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

Для C это предусмотрено в разделе 6.4.5, параграф 6:

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

А для C ++ в разделе 2.14.5, параграфы 8-11:

8 Обычные строковые литералы и строковые литералы UTF-8 также называются узкими строковыми литералами. Узкий строковый литерал имеет тип «массив из n const char», где n - размер строки, как определено ниже, и имеет статическую продолжительность хранения (3.7).

9 Строковый литерал, начинающийся с u, например u"asdf", является char16_tстроковым литералом. char16_tСтроковый литерал имеет тип «массив п const char16_t», где п размер строки , как определено ниже; он имеет статическую продолжительность хранения и инициализируется заданными символами. Один c-char может создать более одного char16_tсимвола в форме суррогатных пар.

10 Строковый литерал, начинающийся с U, например U"asdf", является char32_tстроковым литералом. char32_tСтроковый литерал имеет тип «массив п const char32_t», где п размер строки , как определено ниже; он имеет статическую продолжительность хранения и инициализируется заданными символами.

11 Строковый литерал, начинающийся с L, например L"asdf", является широким строковым литералом. Широкий строковый литерал имеет тип «массив из n const wchar_t», где n - размер строки, как определено ниже; он имеет статический срок хранения и инициализируется заданными символами.


К вашему сведению: этот ответ был объединен с stackoverflow.com/questions/16470959/…
Shog9

14

Строковые литералы действительны для всей программы (и не выделяются не стеку), поэтому они будут действительны.

Кроме того, строковые литералы доступны только для чтения, поэтому (для хорошего стиля), возможно, вам следует изменить fooнаconst char *foo(int)


Что, если пользователь возвращает что-то вроде этого. char * a = & "abc"; вернуть; Это будет недействительно?
Эшвин

&"abc"нет char*. это адрес массива, и его тип char(*)[4]. Однако либо return &"abc";и char *a="abc";return a;действительны.
asaelr 02

@asaelr: На самом деле, это больше, чем просто для хорошего стиля , подробности читайте в моем ответе.
Alok Save

@Als Ну, если он напишет всю программу, он сможет избежать изменения строки без записи const, и это будет полностью законно, но все равно плохой стиль.
asaelr 02

если это справедливо для всей программы, зачем нам его использовать?
TomSawyer

7

Да, это действительный код, см. Случай 1 ниже. Вы можете безопасно возвращать строки C из функции, по крайней мере, следующими способами:

  • const char*в строковый литерал. Его нельзя изменить и не должен освобождать вызывающий абонент. Это редко бывает полезно с целью возврата значения по умолчанию из-за проблемы с освобождением, описанной ниже. Это может иметь смысл, если вам действительно нужно где-то передать указатель на функцию, поэтому вам нужна функция, возвращающая строку.

  • char*или const char*в буфер статических символов. Вызывающий абонент не должен освобождать его. Его можно изменить (либо вызывающим, если не const, либо функцией, возвращающей его), но функция, возвращающая это, не может (легко) иметь несколько буферов, поэтому она (легко) не является потокобезопасной, и вызывающей стороне может потребоваться чтобы скопировать возвращаемое значение перед повторным вызовом функции.

  • char*в буфер, выделенный с помощью malloc. Его можно изменить, но обычно он должен быть явно освобожден вызывающей стороной и имеет накладные расходы на выделение кучи. strdupотносится к этому типу.

  • const char*или char*в буфер, который был передан в качестве аргумента функции (возвращаемый указатель не должен указывать на первый элемент буфера аргументов). Он оставляет ответственность за управление буфером / памятью вызывающей стороне. Многие стандартные строковые функции относятся к этому типу.

Одна из проблем заключается в том, что их смешивание в одной функции может быть сложным. Вызывающий должен знать, как он должен обрабатывать возвращаемый указатель, как долго он действителен, и должен ли вызывающий объект освободить его, и нет (хорошего) способа определить это во время выполнения. Таким образом, вы не можете, например, иметь функцию, которая иногда возвращает указатель на буфер, выделенный кучей, который необходим вызывающему free, а иногда указатель на значение по умолчанию из строкового литерала, который вызывающий не должен free.


К вашему сведению: этот ответ был объединен с stackoverflow.com/questions/16470959/…
Shog9

6

Хороший вопрос. В общем, вы были бы правы, но ваш пример - исключение. Компилятор статически выделяет глобальную память для строкового литерала. Следовательно, адрес, возвращаемый вашей функцией, действителен.

То, что это так, - довольно удобная особенность C, не так ли? Это позволяет функции возвращать предварительно составленное сообщение, не заставляя программиста беспокоиться о памяти, в которой хранится сообщение.

См. Также правильное наблюдение @ asaelr const.


: Что делать, если пользователь возвращает что-то вроде этого. char * a = & "abc"; вернуть; Это будет недействительно?
Эшвин

Правильно. Фактически, можно просто написать const char *a = "abc";, опуская &. Причина в том, что строка в двойных кавычках преобразуется в адрес своего начального символа.
чт,

3

Локальные переменные действительны только в той области, в которой они объявлены, однако вы не объявляете никаких локальных переменных в этой функции.

Совершенно верно возвращать указатель на строковый литерал из функции, поскольку строковый литерал существует на протяжении всего выполнения программы, как staticи глобальная переменная или.

Если вы беспокоитесь о том, что то, что вы делаете, может быть недопустимым undefined, вам следует включить предупреждения компилятора, чтобы увидеть, действительно ли что-то вы делаете неправильно.


Что, если пользователь возвращает что-то вроде этого. char * a = & "abc"; вернуть; Это будет недействительно?
Эшвин

@Ashwin: &"abc"не относится к типу char*, однако , как "abc"и &"abc"действуют на протяжении всего выполнения программы.
AusCBloke

2

strникогда не будет висячим указателем, поскольку он указывает на статический адрес, в котором находятся строковые литералы.

Это будет в основном Когда он будет загружен, доступен только для чтения и будет глобальным для программы.

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


К вашему сведению: этот ответ был объединен с stackoverflow.com/questions/16470959/…
Shog9

если он никогда не будет болтаться, мне нужно его вывести? Нет?
TomSawyer

0

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


Что, если пользователь возвращает что-то вроде этого. char * a = & "abc"; вернуть; Это будет недействительно?
Эшвин

0

В показанном выше примере вы фактически возвращаете выделенные указатели на любую функцию, которая вызывает указанное выше. Таким образом, он не стал бы локальным указателем. Причем для указателей, которые необходимо вернуть, память выделяется в глобальном сегменте.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.