«время жизни» строкового литерала в C - PullRequest
77 голосов
/ 02 апреля 2012

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

char *foo( int rc ) 
{
    switch (rc) 
    {
      case 1:           return("one");
      case 2:           return("two");
      default:           return("whatever");
    }
}

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

Я немного озадачен временем существования локальной переменной var.Кто-нибудь может дать мне хорошее разъяснение?

Ответы [ 9 ]

80 голосов
/ 02 апреля 2012

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

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

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

const char*p = "string"; 

вместо

char*p = "string";    

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

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

#include<string.h> 
int main() 
{ 
    char *str1 = "string Literal"; 
    const char *str2 = "string Literal"; 
    char source[]="Sample string"; 

    strcpy(str1,source);    //No warning or error just Uundefined Behavior 
    strcpy(str2,source);    //Compiler issues a warning 

    return 0; 
} 

Выход:

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

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


РЕДАКТИРОВАТЬ: Чтобы ответить на вопрос, задаваемый несколькими пользователями здесь:

Как обстоят дела с целочисленными литералами?
Другими словами, действителен ли этот код:

int *foo()
{
    return &(2);
} 

Ответ: Нет, этот код недействителен, он некорректен и выдаст ошибку компилятора.
Что-то вроде:

prog.c:3: error: lvalue required as unary ‘&’ operand

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


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

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

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

74 голосов
/ 10 мая 2013

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

Для 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 имеет тип «массив из n const char16_t», где n - размер строки, как определено ниже;он имеет статическую длительность хранения и инициализируется заданными символами.Один символ c-char может выдавать более одного char16_t символа в форме суррогатных пар.

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

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

14 голосов
/ 02 апреля 2012

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

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

7 голосов
/ 10 мая 2013

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

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

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

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

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

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

6 голосов
/ 02 апреля 2012

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

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

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

3 голосов
/ 02 апреля 2012

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

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

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

2 голосов
/ 10 мая 2013

str никогда не будет висящим указателем. Because it points to static address где находятся строковые литералы. Это будет в основном readonly и global для программы, когда она будет загружена. Даже если вы попытаетесь освободить или изменить, он будет выбрасывать segmentation fault на платформах с защитой памяти .

0 голосов
/ 02 апреля 2012

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

Спасибо,

Viharri PL V.

0 голосов
/ 02 апреля 2012

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

...