Volgograd

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

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

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


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

SergeFukanchik/StackTrick


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

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

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


Ответ


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

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

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

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


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

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