Volgograd

Волгоград Linux User Group

Организована 23 ноября 2002 года

Проект заморожен Птн Июл 6 02:11:14 MSD 2012


Вход:  Пароль:  

SergeFukanchik/StackTrick


Это старая версия SergeFukanchik/StackTrick за 2004-11-19 18:30:07..

Как работает стек?

Оглавление документа

Вопрос: почему это работает?


Ответ

Стек


Структура данных «Стек» ведет себя как стопка фарфоровых тарелок. Т.е. ложить и убирать значения можно только сверху. Вставить дополнительное значение в середину или в самый низ стопки невозможно. Для подобных манипуляций существуют другие структуры данных.

Автоматические переменные


В языке Си есть следующие классы переменных:
  1. Статические.
  2. Автоматические.
  3. Динамические.

  1. Статические переменные живут в специальной области памяти, которую выделяет загрузчик программы в момент запуска.
  2. Автоматические переменные живут на стеке (см. раздел «Стек»), который создает для программы опять же загрузчик в момент запуска.
  3. Динамические переменные живут в специальной области памяти, которой управляет сама программа по мере необходимости запрашивая или отдавая ее операционной системе.

Правила инициализации значениями таких переменных:
  1. Статические инициализируются во время запуска программы загрузчиком в состояние в котором все биты выставлены в 0 если не указано конкретное значение.
  2. Автоматические инициализируются в неопределенное значение в момент создания если не было указано конкретное значение.
  3. Динамические инициализируются в неопределенное значение в момент создания. Затем необходима ручная инициализация.

Не будем вдаваться в ужасающие подробности, нас интересуют автоматические переменные. Итак: переменная test это автоматическая переменная, которая содержит указатель на char. Поскольку ее начальное значение не указано, она содержит неопределенное (неинициализированное) значение. Поскольку для физической памяти не существует неопределенного состояния даже в неинициализированной переменной всегда есть какое-то значение, также, поскольку память не безгранична и постоянно повторно используется, это значение обычно остается от предыдущих вычислений.

Вот пример который может пролить свет на все вышесказанное:


Этот код выведет на печать BAR=This is test!. Но не обязательно. Это зависит от реализации. И от компилятора. И от операционной системы. И от фазы луны тоже. Такой код писать нельзя. И доверяться ему нельзя. Мало того, обычно компиляторы ругаются при попытке скомпилировать такой код и говорят что используется неинициализированная переменная.

Что же тут произошло? В момент вызова функции f1 была выделена (неинициализированная) память для переменной foo, затем этой переменной было присвоено значение указателя на (динамическую) область памяти которая содержит текст Ahis is test!. Затем выполнение функции f1 завершается, и память занятая переменной foo освобождается. Память была освобождена, но значение осталось! Ведь аппаратура памяти не имеет неопределенного состояния для своих ячеек. Поэтому значение там и осталось. Теперь мы снова в функции main и никак не трогая стек тут же вызываем функцию f2. Для нее будет повторно выделен тот же участок памяти на стеке что и чуть раньше был выделен функции f1. А там хранится старое значение, которое в нашем случае приобрело новое имя – bar, потому что переменная bar не была явно инициализирована другим значением. Мы проводим разные манипуляции с этим значением и даже освобождаем память, которую занимает неименованное значение на которое он указывает.
Можно предположить, что компилятор мог бы устанавливать освобождаемую память в определенное заренее значение (которое было бы ошибкой). Но во-первых не совсем ясно какое именно значение это должно быть, во-вторых это отнимает много времени и поэтому сильно замедляет работу программы. Некоторые отладчики памяти (например ElectricFence, Valgrind) все-таки проводят такую инициализацию и ценой сильного замедления работы программы помогают найти где же именно допущена подобная ошибка.