Основы программирования

Аппаратный стек и локальные переменные подпрограммы


Поскольку аппаратный стек располагается в оперативной памяти, в нем можно размещать обычные переменные программы. Размещение локальных переменных в стеке обладает рядом преимуществ по сравнению со статическим размещением переменных в фиксированных ячейках оперативной памяти. Как уже говорилось выше, это позволяет организовывать рекурсию. Кроме того, в современных архитектурах принципиальное значение имеет поддержка параллельных процессов, работающих над общими статическими переменными. Это так называемые легковесные процессы, или нити (Thread), работающие параллельно в рамках одной программы. На использовании нитей, например, основана работа всех графических приложений в системе Microsoft Windows 32: одна нить обрабатывает сообщения графической системы (нажатия на клавиатуру и кнопки мыши, перерисовка окон, выборка команд из меню и т.п.), другие нити занимаются вычислениями, сетевым обменом, анимацией и т.п.

Различные нити работают параллельно над общими статическими данными, совершая таким образом некоторую совместную работу. При этом одна и та же подпрограмма может вызываться из разных нитей. В отличие от статических переменных, которые являются общими для всех нитей, для каждой нити выделяется свой отдельный стек. При использовании нитей очень важно, чтобы локальные переменные подпрограммы располагались в стеке. Иначе было бы невозможно параллельно вызывать одну и ту же подпрограмму из разных нитей: повторный вызов подпрограммы, уже работающей в рамках другой нити, разрушил бы статический набор локальных переменных этой подпрограммы. А при использовании стека наборы локальных данных одной и той же подпрограммы, вызываемой из разных нитей, различны, поскольку они располагаются в разных стеках. Таким образом, разные нити работают с разными наборами локальных переменных, не мешая друг другу.

Рассмотрим более подробно, как размещаются локальные переменные подпрограммы в стеке, на примере языка Си. В Си подпрограммы называются функциями. Функция может иметь аргументы и локальные переменные, т.е.
переменные, существующие только в процессе выполнения функции. Рассмотрим для примера функцию ?, зависящую от двух входных аргументов x и y целого типа, в которой используются три локальные переменные a, b и c также целого типа. Функция возвращает целое значение.

int f(int x, int y) { int a, b, c; ... }

Пусть в некотором месте программы вызывается функция ? с аргументами x = 222, y = 333:

z = f(222, 333);

Вызывающая программа помещает фактические значения аргументов x и y функции ? в стек, при этом на вершине стека лежит первый аргумент функции, под ним — второй аргумент. Вызов функции транслируется в следующие команды:

push 333 push 222 call ?

Обратите внимание, что в стек сначала помещается второй аргумент функции, затем первый, в результате на вершине стека оказывается первый аргумент. При выполнении инструкции вызова call в стек помещается также адрес возврата.

В момент начала работы функции ? cтек имеет следующий вид:

адрес возврата<=SP
222
333
....
На вершине стека лежит адрес возврата, под ним — фактическое значение аргумента x, затем фактическое значение аргумента y.

Перед началом работы функция ? должна захватить в стеке область памяти под свои локальные переменные a, b, c. В языке Си принято следующее соглашение: адрес блока локальных переменных функции в момент ее работы помещается в специальный регистр процессора, который называется FP, от англ. Frame Pointer — указатель кадра. (В процессоре Intel 80386 роль указателя кадра выполняет регистр EBP.) В первую очередь функция ? сохраняет в стеке предыдущее значение регистра FP. Затем значение указателя стека копируется в регистр FP. После этого функция ? захватывает в стеке область памяти размером в 3 машинных слова под свои локальные переменные a, b, c. Для этого функция ? просто уменьшает значение регистра SP на 12 (три машинных слова равны двенадцати байтам). Таким образом, начало функции ? состоит из следующих команд:

push FP FP := SP SP := SP ? 12

После захвата кадра локальных переменных стек выглядит следующим образом.



c<=SP
b
a
старое значение FP<=FP
адрес возврата
x=222
y=333
...
Аргументы и локальные переменные функции ? адресуются относительно регистра FP. Так, аргумент x имеет адрес FP+8, аргумент y - адрес FP+12. Переменная a имеет адрес FP-4, переменная b - адрес FP-8, переменная c - адрес FP-12.

По окончании работы функция ? сначала увеличивает указатель стека на 12, удаляя таким образом из стека свои локальные переменные a, b, c. Затем старое значение FP извлекается из стека и помещается в FP (таким образом, регистр FP восстанавливает свое значение до вызова функции ?). После этого осуществляется возврат в вызывающую программу: адрес возврата снимается со стека и управление передается по адресу возврата. Результат функции ? передается через нулевой регистр.

R0 := результат функции SP := SP +12 pop FP return

Вызывающая программа удаляет из стека фактические значения аргументов x и y, помещенные в стек перед вызовом функции ?.
Содержание раздела