Области применения указателя в C

В следующем коде объяснение сбоя при печати чего-либо состоит в том, что указатель, возвращаемый get_message(), находится вне области видимости:

char *get_message() {
    char msg [] = "Aren’t pointers fun?";
    return msg ;
}

int main (void) {
    char *foo = get_message();
    puts(foo);
    return 0;
}

При запуске в gdb выясняется, что данные в позиции foo - это строка "Не интересны ли указатели?":

Old value = 0x0
New value = 0x7fffffffde60 "Aren’t pointers fun?"

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

Поэтому: почему ничего не выводится?

РЕДАКТИРОВАТЬ: Спасибо за ваши ответы: я запустил исходный код до конца в GDB, вызов put действительно изменяет данные по адресу, где хранилась foo.

(gdb) p foo
$1 = 0x7fffffffde60 "Aren’t pointers fun?"
(gdb) n

11      return 0;
(gdb) p foo
$2 = 0x7fffffffde60 "`\336\377\377\377\177"

Интересно, что код напечатал сообщение, когда я изменил код для change_msg() на:

char *get_message() {
        char *msg = "Aren’t pointers fun?";
    return msg ;
} 

В этом случае данные в foo (адрес 0x4005f4 - меньший размер адреса что-нибудь значат?) Остаются неизменными во всем коде. Было бы здорово узнать, почему это меняет поведение

5 ответов

Решение

Переменная msg расположена в стеке get_message()

char msg [] = "Aren’t pointers fun?";

Как только get_message() возвращается, стек для этого метода разрушается. В этот момент нет никаких гарантий того, что находится в памяти, на которую вернул указатель foo сейчас указывает на.

Когда метод put () вызывается, стек, вероятно, изменяется, перезаписывая "Не забавный указатель".

Настоящий вопрос здесь не в том, "почему это не работает?". Вопрос заключается в том, "почему кажется, что строка существует даже после возвращения из get_message а потом все равно не сработает?

Чтобы уточнить, давайте посмотрим на main функция снова, с двумя комментариями для справки:

int main (void) {
    char *foo = get_message();
    /* point A */
    puts(foo);
    /* point B */
    return 0;
}

Я только что скомпилировал и запустил это под GDB. Действительно, в point A, когда я распечатал значение переменной foo в GDB GDB показал мне, что он указал на строку "Aren’t pointers fun?", Но потом, puts не удалось распечатать эту строку. И тогда, в point B, если я снова распечатал foo в GDB эта строка больше не была.

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


В ответ на дополнительный вопрос ФП: когда мы

char *get_message() {
    char msg [] = "Aren’t pointers fun?";
    return msg ;
}

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

char * msg = "Aren’t pointers fun?";

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


Суть в том, что массивы и указатели разные. Очень сильно отличается. Линия

char arr[] = "Hello, world!";

почти не имеет отношения к очень похожей линии

char *ptr = "Hello, world!";

Теперь они одинаковы в том, что вы можете сделать оба

printf("%s\n", arr);

а также

printf("%s\n", ptr);

Но если вы попытаетесь сказать

arr = "Goodbye";    /* WRONG */

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

strcpy(arr, "Goodbye");

Но если вы попробуете strcpy вещь с указателем:

strcpy(ptr, "Goodbye");    /* WRONG */

теперь это не работает, потому что строковая константа, которая ptr Очки не подлежат записи. В случае указателя вы можете (и часто должны) использовать простое присваивание:

ptr = "Goodbye";

и в этом случае нет проблем с установкой более длинной строки:

ptr = "Supercalafragalisticexpialadocious";

Это основные различия, но, как показывает этот вопрос, еще одно большое отличие состоит в том, что массив arr не может быть полезно объявлен и возвращен из функции (если вы не сделаете это static), пока указатель ptr Можно.

Вполне вероятно, что зовет puts изменяет стек и перезаписывает строку.

Просто возвращаясь из get_message оставляет строку без изменений, но освобождает ее, т.е. ее пространство памяти доступно для повторного использования.

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

Доступ к нему приводит к неопределенному поведению. Все может случиться.

В твоем случае память о бывшем msg Кажется, уже перезаписано 0.

И это не про "размах". Вы можете исправить свой код, сделав msgstatic, Это не меняет область действия, но ее время жизни (или продолжительность хранения).

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

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

Если вы должны сделать что-то вроде этого, я видел, как это делается с использованием статического:

static char * message = "I love static pointers";

Редактировать: несмотря на упоминание о том, что МОЖЕТ все еще быть в стеке, НИКОГДА НИКОГДА НЕ ПОГОДИТЕ Большинство языков даже не допустят этого.

Другие вопросы по тегам