Области применения указателя в 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.
И это не про "размах". Вы можете исправить свой код, сделав msg
static
, Это не меняет область действия, но ее время жизни (или продолжительность хранения).
В вашей функции getMessage память, используемая вашим сообщением, находится в стеке, а не в куче. Это по-прежнему указатель, просто на место в стеке. Как только функция возвращается, стек изменяется (чтобы получить IP-адрес возврата и т. Д.), Когда это означает, что, хотя сообщение МОЖЕТ быть в том же месте в памяти, нет абсолютно никаких гарантий. Если что-то еще помещает что-либо в стек (например, другой вызов функции), то, скорее всего, оно будет переопределено. Ваше сообщение пропало.
Лучшим подходом было бы динамическое выделение памяти с помощью malloc, чтобы убедиться, что строка находится в куче (хотя это приводит к проблеме того, кто владеет указателем и отвечает за его освобождение).
Если вы должны сделать что-то вроде этого, я видел, как это делается с использованием статического:
static char * message = "I love static pointers";
Редактировать: несмотря на упоминание о том, что МОЖЕТ все еще быть в стеке, НИКОГДА НИКОГДА НЕ ПОГОДИТЕ Большинство языков даже не допустят этого.