Как это может быть? Разве память локальной переменной не доступна вне ее функции?
Вы снимаете номер в отеле. Вы кладете книгу в верхний ящик тумбочки и ложитесь спать. Вы выезжаете на следующее утро, но «забыли» вернуть свой ключ. Ты крадешь ключ!
Через неделю вы возвращаетесь в отель, не регистрируетесь, крадетесь в свою старую комнату с украденным ключом и смотрите в ящик. Ваша книга все еще там. Поразительно!
Как это может быть? Разве содержимое ящика гостиничного номера недоступно, если вы не арендовали номер?
Ну, очевидно, что сценарий может произойти в реальном мире без проблем. Там нет таинственной силы, которая заставляет вашу книгу исчезнуть, когда вам больше не разрешено находиться в комнате. Также не существует таинственной силы, которая мешает вам войти в комнату с украденным ключом.
Администрация отеля не обязана удалять вашу книгу. Вы не заключили с ними контракт, в котором говорилось, что если вы оставите вещи позади, они уничтожат их для вас. Если вы незаконно вернетесь в свою комнату с украденным ключом, чтобы вернуть его, сотрудники службы безопасности отеля не обязаны отлавливать вас подкрадываясь. Вы не заключили с ними контракт, в котором говорилось: «Если я попытаюсь пробраться обратно в мой номер позже, вы обязаны остановить меня. " Скорее, вы подписали с ними договор, в котором говорилось: «Я обещаю не пробраться обратно в мою комнату позже», договор, который вы нарушили .
В этой ситуации все может случиться . Книга может быть там - вам повезло. Там может быть чужая книга, а ваша может быть в печи отеля. Кто-то может быть там, когда вы входите, разрывая вашу книгу на части. Отель мог бы убрать стол и забронировать полностью и заменить его гардеробом. Весь отель может быть снесен и заменен футбольным стадионом, и вы умрете от взрыва, пока будете красться.
Вы не знаете, что произойдет; Когда вы выписались из отеля и позже украли ключ для нелегального использования, вы отказались от права жить в предсказуемом и безопасном мире, потому что решили нарушить правила системы.
C ++ не является безопасным языком . Это позволит вам весело нарушить правила системы. Если вы попытаетесь сделать что-то незаконное и глупое, например, вернуться в комнату, в которой вам не разрешено находиться, и порыться в столе, которого там больше нет, C ++ вас не остановит. Более безопасные языки, чем C ++, решают эту проблему, ограничивая ваши возможности - например, благодаря более строгому контролю над ключами.
ОБНОВИТЬ
Боже мой, этот ответ привлекает много внимания. (Я не уверен почему - я посчитал это просто «забавной» небольшой аналогией, но что угодно.)
Я подумал, что было бы уместно немного обновить это, добавив несколько технических соображений.
Компиляторы занимаются созданием кода, который управляет хранением данных, которыми манипулирует эта программа. Существует множество различных способов генерации кода для управления памятью, но со временем укоренились два основных метода.
Во-первых, иметь своего рода «долгоживущую» область хранения, в которой «время жизни» каждого байта в хранилище, т. Е. Период времени, когда он корректно связан с некоторой программной переменной, не может быть легко предсказано заранее. времени. Компилятор генерирует вызовы в «диспетчере кучи», который знает, как динамически распределять хранилище, когда оно необходимо, и восстанавливать его, когда оно больше не нужно.
Второй способ заключается в том, чтобы иметь «недолговечную» область хранения, где время жизни каждого байта хорошо известно. Здесь время жизни соответствует шаблону «вложения». Самая долгоживущая из этих кратковременных переменных будет размещена перед любыми другими недолговечными переменными и будет освобождена последней. Короткоживущие переменные будут размещены после самых долгоживущих и будут освобождены перед ними. Время жизни этих короткоживущих переменных «вложено» в время жизни долгоживущих.
Локальные переменные следуют последней схеме; когда метод вводится, его локальные переменные оживают. Когда этот метод вызывает другой метод, локальные переменные нового метода оживают. Они будут мертвы до того, как локальные переменные первого метода будут мертвы. Относительный порядок начала и окончания времен хранения хранилищ, связанных с локальными переменными, может быть разработан заранее.
По этой причине локальные переменные обычно генерируются как хранилище в структуре данных «стека», потому что у стека есть свойство, которое первое, что было помещено в него, будет последним извлеченным объектом.
Это похоже на то, что отель решает только сдавать комнаты в аренду последовательно, и вы не можете проверить, пока все с номером комнаты выше, чем вы проверили.
Итак, давайте подумаем о стеке. Во многих операционных системах вы получаете один стек на поток, и стек выделяется для определенного фиксированного размера. Когда вы вызываете метод, материал помещается в стек. Если вы затем передаете указатель на стек обратно из вашего метода, как это делает оригинальный постер, это просто указатель на середину какого-то полностью действительного блока памяти в миллион байт. По нашей аналогии вы выезжаете из отеля; когда вы это сделаете, вы только что вышли из занятой комнаты с наибольшим номером. Если никто не зарегистрируется после вас, и вы незаконно вернетесь в свой номер, все ваши вещи гарантированно останутся в этом конкретном отеле .
Мы используем стеки для временных магазинов, потому что они действительно дешевые и простые. Реализация C ++ не обязана использовать стек для хранения локальных данных; это может использовать кучу. Это не так, потому что это замедлит работу программы.
Реализация C ++ не обязана оставлять нетронутым мусор, оставленный в стеке, чтобы впоследствии вы могли незаконно вернуться к нему позже; для компилятора совершенно законно генерировать код, который обнуляет все в «комнате», которую вы только что освободили. Это не потому, что опять же, это будет дорого.
Реализация C ++ не требуется для обеспечения того, чтобы при логическом сжатии стека адреса, которые раньше были действительными, по-прежнему отображались в памяти. Реализация позволяет сообщить операционной системе: «Мы закончили с использованием этой страницы стека. Пока я не скажу иначе, выдайте исключение, которое уничтожит процесс, если кто-нибудь коснется ранее действующей страницы стека». Опять же, реализации на самом деле не делают этого, потому что это медленно и не нужно.
Вместо этого реализации позволяют вам делать ошибки и сойти с рук. Большую часть времени. Пока однажды что-то действительно ужасное не пойдет не так и процесс не взорвется.
Это проблематично. Есть много правил, и их легко нарушить случайно. Я, конечно, много раз. И что еще хуже, проблема часто появляется только тогда, когда обнаруживается, что память искажена на миллиарды наносекунд после того, как произошло повреждение, когда очень трудно определить, кто его испортил.
Более безопасные для памяти языки решают эту проблему, ограничивая ваши возможности. В «нормальном» C # просто нет возможности взять адрес локального и вернуть его или сохранить на потом. Вы можете взять адрес локального, но язык продуманно разработан так, что его невозможно использовать после окончания срока действия локального. Чтобы взять локальный адрес и передать его обратно, вы должны перевести компилятор в специальный «небезопасный» режим и поместить слово «небезопасный» в вашу программу, чтобы обратить внимание на тот факт, что вы, вероятно, делаете что-то опасное, что может нарушать правила.
Для дальнейшего чтения:
address of local variable ‘a’ returned
; Valgrind показываетInvalid write of size 4 [...] Address 0xbefd7114 is just below the stack ptr