Volgograd

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

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

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


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

SergeFukanchik/StackTrick


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

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

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


Ответ

Стек


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

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


В языке Си есть следующие классы переменных:
  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) все-таки проводят такую инициализацию и ценой сильного замедления работы программы помогают найти где же именно допущена подобная ошибка.

Функция main


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


[ Программирование ]