Можно ли получить доступ к памяти локальной переменной вне её области видимости?
У меня есть следующий код:
#include <iostream>
int * foo()
{
int a = 5;
return &a;
}
int main()
{
int* p = foo();
std::cout << *p;
*p = 8;
std::cout << *p;
}
Код выполняется без каких-либо исключений во время выполнения! Вывод был 58
.
Как это возможно? Разве память локальной переменной недоступна за пределами своей функции?
5 ответ(ов)
Вы просто читаете и записываете в память, которая ранее принадлежала переменной a
. Теперь, когда вы выходите из функции foo
, это всего лишь указатель на какую-то случайную область памяти. В вашем примере эта область памяти все еще существует и в данный момент ничем другим не используется.
Вы не ломаете ничего, продолжая использовать эту память, и пока никто еще не перезаписал ее. Поэтому 5
все еще там. В реальной программе эта память будет переработана практически сразу, и вы можете разрушить что-то, делая это (хотя симптомы могут проявиться гораздо позже!).
Когда вы возвращаетесь из foo
, вы сообщаете операционной системе, что больше не используете эту память, и она может быть перераспределена для других нужд. Если вам повезет, и память так и не будет перераспределена, а ОС не поймает вас на повторном использовании, то вам удастся ускользнуть от последствий. Скорее всего, вы в конце концов запишете что-то в ту область памяти, которая будет использоваться другим процессом.
Если вы задаетесь вопросом, почему компилятор не жалуется, то причина, вероятно, в том, что функция foo
была устранена оптимизацией. Обычно он предупреждает об этом. В C предполагается, что вы знаете, что делаете, и технически вы здесь не нарушаете область видимости (нет ссылки на a
вне функции foo
), вы лишь нарушаете правила доступа к памяти, что вызывает предупреждение, а не ошибку.
В кратце: это обычно не сработает, но иногда бывает случайно.
Потому что пространство для хранения еще не было перезаписано. Не стоит полагаться на это поведение.
Небольшое дополнение ко всем ответам:
Если вы сделаете что-то вроде этого:
#include <stdio.h>
#include <stdlib.h>
int * foo(){
int a = 5;
return &a;
}
void boo(){
int a = 7;
}
int main(){
int * p = foo();
boo();
printf("%d\n", *p);
}
Вывод, вероятно, будет: 7.
Это происходит потому, что после возврата из функции foo()
стек освобождается и затем переиспользуется в boo()
.
Если вы разберете исполняемый файл, это будет видно явно.
Ваша проблема не имеет отношения к области видимости. В том коде, который вы показываете, функция main
не видит имена из функции foo
, поэтому вы не можете получить доступ к a
в foo
напрямую с помощью этого имени вне foo
.
Ваш вопрос касается того, почему программа не выдает ошибку при обращении к недопустимой памяти. Это связано с тем, что стандарты C++ не устанавливают четкой границы между недопустимой и допустимой памятью. Обращение к чему-то из освободившегося стека иногда вызывает ошибку, а иногда нет. Это зависит от обстоятельств. Не следует рассчитывать на данное поведение. Предполагается, что это всегда будет приводить к ошибке во время программирования, но можно считать, что это никогда не вызовет ошибку при отладке.
Это работает, потому что стек не был изменён (пока) с того момента, как a
был помещён туда. Вызовите несколько других функций (которые также вызывают другие функции) перед тем, как снова получить доступ к a
, и, вероятно, вам не повезёт так же... 😉
В чем разница между #include <filename> и #include "filename"?
Когда использовать виртуальные деструкторы?
Какова разница между 'typedef' и 'using'?
Циклы в программном обеспечении для семейных деревьев
Что означает 'const' в конце объявления метода класса?