"срок службы" строкового литерала в C

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

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

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

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

Ответ 1

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

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

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

const char*p = "string"; 

вместо <

char*p = "string";    

На самом деле, в С++ устаревший объявляет строковый литерал без 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 отбрасывает квалификаторы из целевого типа указателя

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


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

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

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

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

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

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


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

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

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

Ответ 2

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

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

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

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

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

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

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

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

Ответ 3

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

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

Ответ 4

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

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

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

Ответ 5

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

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

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

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

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

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

Ответ 6

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

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

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

Ответ 7

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

Ответ 8

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

Ответ 9

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

Благодарим вас,

Viharri P L V.