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