"время жизни" строкового литерала в языке Си



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



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


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



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

816   9  

9 ответов:

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

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

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

const char*p = "string"; 

Вместо,

char*p = "string";    

Фактически, в C++ не рекомендуется объявлять a строковый литерал без 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: предупреждения рассматриваются как ошибки
еда.c: в функции ' main’:
еда.c: 9: ошибка: передача аргумента 1 из’ strcpy ' отбрасывает квалификаторы из Указателя целевого типа

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

EDIT: чтобы ответить на вопрос, заданный несколькими пользователями здесь:

Что происходит с интегральными литералами?
Другими словами, действителен ли этот код:

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

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

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

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


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

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

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

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

Для С, что предусмотрено в пункте 6 раздела 6.4.5:

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

И для C++ в разделе 2.14.5, пункты 8-11:

8 обычные строковые литералы и строковые литералы UTF-8 также называются узкими строковыми литералами. Узкий строковый литерал имеет тип " array of 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 имеет тип " array of n const char32_t", где n-размер строки, как определено ниже; он имеет статическую длительность хранения и инициализируется заданным персонажи. Строковый литерал, начинающийся с буквы L, например L"asdf", является широким строковым литералом. Широкий строковый литерал имеет тип " array of n const wchar_t", где n-размер строки, как определено ниже; он имеет статическую длительность хранения и инициализируется заданными символами.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Благодарю Вас,

Вихарри П Л В.

Comments

    Ничего не найдено.