Объем char * по сравнению с массивом char в C - PullRequest
4 голосов
/ 17 июня 2020

Эти две формы одной и той же переменной, когда они определены в рамках функционального блока, должны, я думаю, иметь идентичную область видимости, т.е. внутри функциональных блоков {...}, где они определены:

char str1[] = "int_1 < int_2";

char *str1 = "int_1 < int_2";  

Но по моим наблюдениям, char * живет за пределами функций, а char [] перестает существовать. Имя символа str1 в обоих случаях указывает на место в памяти, где создается переменная, так почему же один кажется живет за пределами функции, а другой - нет? Для проверки этого поведения можно использовать следующий код: (Изменение #define с 0 на 1 выбирает одну форму вместо другой для иллюстрации.)

Обратите внимание, что хотя модификатор static может использоваться для изменения области видимости, здесь она специально не используется для наблюдения за поведением без нее.

#define DO (1)  //define as either 1 or 0

char * compare_int(int x1, int x2);

int main(void)
{
    int a = 0;
    int b = 0;
    int c = '\n';

    srand(clock()/CLOCKS_PER_SEC);

    while(c != 'q')
    {
        a = rand()%3;
        b = rand()%3;
        printf("%s\n( enter 'q' to exit. )\n\n", compare_int(a, b));
        c = getchar();
    }
    return 0;
}

char * compare_int(int x, int y) 
{
    printf("%d    %d\n", x, y);

#if(DO)
    char str1[] = "int_1 < int_2";
    char str2[] = "int_1 == int_2";    
    char str3[] = "int_1 > int_2";
#else
    char *str1 = "int_1 < int_2";
    char *str2 = "int_1 == int_2";    
    char *str3 = "int_1 > int_2";
#endif  

    return x < y ? (str1) : x == y ? (str2) : (str3);

}

Я прочитал этот , и он отвечает на некоторые ключевые части этого вопроса, но комментирует любой UB в моем коде и / или ссылки на C99 или более новые стандартные указания к параграфам, в которых проводится различие между этими двумя формами, также будут приняты во внимание.

Ответы [ 5 ]

4 голосов
/ 17 июня 2020

Это:

char str1[] = "int_1 < int_2";

Определяет массив, инициализированный заданным строковым литералом. Если вы вернете str1, поскольку имя массива распадается до указателя на его первый элемент, вы возвращаете указатель на локальную переменную. Время жизни этой переменной заканчивается, когда функция возвращается, и попытка впоследствии использовать этот адрес вызывает неопределенное поведение .

Это описано в разделе 6.2.4p2 стандарта C :

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

Напротив, это:

char *str1 = "int_1 < int_2";  

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

Время жизни строки литералы указаны в разделе 6.4.5p6 стандарта C :

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

И стат c продолжительность хранения определена в разделе 6.2.4p3:

Объект, идентификатор которого объявлен без
спецификатора класса хранения _Thread_local, и либо с внешней, либо внутренней связью, либо с спецификатор класса хранения static имеет stati c продолжительность хранения . Его время жизни - это полное выполнение программы, и его сохраненное значение инициализируется только один раз перед запуском программы.

1 голос
/ 17 июня 2020

Если вы return указатель из вызываемой функции, вы не возвращаете ссылку на сам указатель.

Вместо этого значение указателя - фактически адрес первого элемента строкового литерала, здесь fe "int_1 < int_2", назначенный ему - возвращается по значению , но не сам указатель по ссылке .

Сам строковый литерал находится в постоянной памяти до завершения программы.


Фактически, как указатель на char (char *), так и массив char (char[]) имеют один и тот же класс хранения auto и видны только функции compare_int (имеют локальную область действия).

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

Значение, используемое в вызове printf(), на самом деле является адресом первого элемента строкового литерала, переданного по значению . Это не имеет ничего общего с указателем в вызываемой функции, здесь strN.

Строковый литерал не связан с указателем c.

Были бы они квалифицированы с помощью спецификатор класса хранения static, тогда их объекты будут существовать в памяти до завершения программы, сохранят свои значения через различные вызовы функций и будут видны везде, где вы получили ссылку на их фактические объекты с помощью переданных на них указателей в вызывающей стороне (s ).

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


Вы можете представить себе это еще лучше, если вы подумаете о указателе в вызываемой функции как о «держателе» или, еще лучше, о «доставщике», например о том, кто дружелюбно доставляет ваши товары с Amazon. Он / она владеет адресом только определенное время, но после этого передает значение другому лицу.

Аналогично происходит при возврате значения адреса из compare_int. Указатель в вызываемой функции strN передает значение адреса вызывающей стороне. Здесь он принимается как аргумент для printf().

1 голос
/ 17 июня 2020

В этих объявлениях с автоматическим c продолжительностью хранения внутри функции

char str1[] = "int_1 < int_2";

char *str1 = "int_1 < int_2";

оба идентификатора имеют одинаковую область действия и не существуют вне функции.

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

Разница в том, что указатель str1 указывает на строковый литерал, который имеет продолжительность хранения c. Таким образом, вы можете вернуть указатель из функции, потому что строковый литерал будет активен, а возвращаемый указатель будет указывать на него.

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

0 голосов
/ 17 июня 2020

Итак, я выполнил ваш код в IDE CodeBlocks и вот что я нашел: -

CASE 1

In compare_int(int x, int y), когда x = 2 & y = 0 , перед объявлением str1, str2, and str3 character arrays(char []), str1, 2 & 3 содержит некоторые мусорные значения, затем они объявляются локально в функции по некоторым адресам памяти и им присваиваются значения.

Затем согласно условию, address of str3 успешно возвращается вызывающей функции, которая является char * по этому адресу. Но когда в printf("%s\n( enter 'q' to exit. )\n\n", compare_int(a, b)); printf() пытается прочитать этот адрес через char *, возвращенный функцией compare_int(), он находит там какое-то мусорное значение. Это означает, что значения массивов str1[], str2[], and str3[] являются локальными для этой функции и не сохраняются за пределами этой функции complex_int().

CASE 0

Когда str1, str2, and str3 являются объявлены и определены как constant strings, их значение возвращается функцией и успешно читается printf(), что означает, что их значение сохраняется между вызовами различных функций.

Заключение:

В соответствии с этим поведением, я могу сказать, что в случае 0 , string literals хранятся в permanent storage area, а локальный char *, который хранится в stack, указывает на это string literal, когда мы return это char *, он возвращает его значение, которое является адресом этого string literal, поэтому оно печатается успешно. С другой стороны, в случае 1 , char [] хранятся в stack и являются локальными для parent function, и их значение не сохраняется между вызовами различных функций. Вот почему они недоступны из main().

0 голосов
/ 17 июня 2020

С char [] ваша локальная переменная, размещенная в стеке, представляет собой массив символов. То, что вы возвращаете из функции, - это указатель на эту локальную переменную. Однако локальная переменная исчезает, как только вы возвращаетесь из функции, а возвращаемый вами указатель продолжает указывать на место в стеке, которое рано или поздно перезаписывается каким-то другим материалом.

С char * ваш локальный переменная - это просто указатель, указывающий на постоянную постоянную. Вы возвращаете копию этого указателя, и это нормально.

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

...