11

Можно ли получить доступ к памяти локальной переменной вне её области видимости?

8

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

#include <iostream>

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

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

Код выполняется без каких-либо исключений во время выполнения! Вывод был 58.

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

5 ответ(ов)

2

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

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

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

Если вы задаетесь вопросом, почему компилятор не жалуется, то причина, вероятно, в том, что функция foo была устранена оптимизацией. Обычно он предупреждает об этом. В C предполагается, что вы знаете, что делаете, и технически вы здесь не нарушаете область видимости (нет ссылки на a вне функции foo), вы лишь нарушаете правила доступа к памяти, что вызывает предупреждение, а не ошибку.

В кратце: это обычно не сработает, но иногда бывает случайно.

1

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

0

Небольшое дополнение ко всем ответам:

Если вы сделаете что-то вроде этого:

#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().

Если вы разберете исполняемый файл, это будет видно явно.

0

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

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

0

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

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь