Можно ли получить доступ к памяти локальной переменной вне ее области? - PullRequest
964 голосов
/ 22 июня 2011

У меня есть следующий код.

#include <iostream>

int * foo()
{
    int a = 5;
    return &a;
}

int main()
{
    int* p = foo();
    std::cout << *p;
    *p = 8;
    std::cout << *p;
}

И код просто выполняется без исключений времени выполнения!

Вывод был 58

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

Ответы [ 20 ]

16 голосов
/ 25 июня 2011

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

16 голосов
/ 23 июня 2011

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

16 голосов
/ 17 августа 2015

Обратите внимание на все предупреждения.Не только исправляйте ошибки.
GCC показывает это предупреждение

предупреждение: адрес локальной переменной 'a' вернул

Это сила C ++.Вы должны заботиться о памяти.С флагом -Werror это предупреждение становится ошибкой, и теперь вы должны его отладить.

15 голосов
/ 25 июня 2011

Вы фактически вызвали неопределенное поведение.

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

ИтакВы изменили не a, а место памяти, где когда-то был a.Эта разница очень похожа на разницу между сбой и не сбой.

13 голосов
/ 25 июня 2011

Может, потому что a - это переменная, временно выделенная на время жизни ее области (функция foo). После возврата из foo память свободна и может быть перезаписана.

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

13 голосов
/ 22 июня 2011

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

Однако это поведение undefined , и вы не должны полагаться на его работу!

11 голосов
/ 24 июня 2011

Вещи с правильным (?) Выводом на консоль могут сильно измениться, если вы используете :: printf, но не cout.Вы можете поиграться с отладчиком в следующем коде (протестировано на x86, 32-битной, MSVisual Studio):

char* foo() 
{
  char buf[10];
  ::strcpy(buf, "TEST”);
  return buf;
}

int main() 
{
  char* s = foo();    //place breakpoint & check 's' varialbe here
  ::printf("%s\n", s); 
}
4 голосов
/ 19 июля 2017

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

Итак, здесь функция foo() возвращает адрес a, а a уничтожается после возврата его адреса. И вы можете получить доступ к измененному значению через этот возвращенный адрес.

Позвольте мне привести пример из реального мира:

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

3 голосов
/ 08 марта 2017

Это «грязный» способ использования адресов памяти. Когда вы возвращаете адрес (указатель), вы не знаете, относится ли он к локальной области действия функции. Это просто адрес. Теперь, когда вы вызвали функцию 'foo', этот адрес (ячейка памяти) для 'a' уже был размещен там в (по крайней мере, пока безопасно) адресуемой памяти вашего приложения (процесса). После того, как функция 'foo' вернулась, адрес 'a' может считаться 'грязным', но он там, не очищен и не нарушен / не изменен выражениями в другой части программы (по крайней мере, в данном конкретном случае) Компилятор C / C ++ не останавливает вас от такого «грязного» доступа (хотя может вас предупредить, если вам не все равно). Вы можете безопасно использовать (обновлять) любую область памяти, которая находится в сегменте данных экземпляра вашей программы (процесса), если вы не защищаете адрес каким-либо способом.

0 голосов
/ 02 мая 2019

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

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

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

Вместо этого рассмотрите этот пример и протестируйте его:

int * foo()
{
   int *x = new int;
   *x = 5;
   return x;
}

int main()
{
    int* p = foo();
    std::cout << *p << "\n"; //better to put a new-line in the output, IMO
    *p = 8;
    std::cout << *p;
    delete p;
    return 0;
}

В отличие от вашего примера, с этим примером вы:

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