Во всех языках программирования есть понятие процедур (методов), имеющих локальные переменные. Эти переменные доступны во время выполнения процедуры, но перестают быть доступными после ее окончания. Возникает вопрос: где должны храниться такие переменные?

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

Вместо этого используется другая стратегия. Для переменных резервируется особая область памяти, которая называется стеком и в которой отдельные переменные не получают абсолютных адресов. Какой-либо регистр, скажем, LV, указывает на базовый адрес локальных переменных для текущей процедуры. Посмотрите на рис. 4.7, а. В данном случае вызывается процедура А с локальными переменными a1, а2 и аЗ, и для этих переменных резервируется участок памяти, начинающийся с адреса, который указывается регистром LV. Другой регистр, SP, указывает на старшее слово локальных переменных процедуры А. Если значение регистра LV равно 100, а слова по 4 байт, то значение SP составляет 108. Для обращения к переменной нужно вычислить ее смещение от адреса LV. Структура данных между LV и SP (включая оба указанных слова) называется фреймом локальных переменных.

А теперь давайте посмотрим, что произойдет, если процедура Л вызывает другую процедуру, например В. Где должны храниться 4 локальные переменные процедуры В (61, 62, 63, 64)? Ответ: в стеке, расположенном над стеком для процедуры А, как показано на рис. 4.7, б. Отметим, что после вызова процедуры регистр LV указывает уже на локальные переменные процедуры В. Обращаться к локальным переменным процедуры В можно по их сдвигу от LV. Если процедура В вызывает процедуру С, то регистры LV и SP снова переопределяются и указывают на местонахождение локальных переменных процедуры С, как показано на рис. 4.7, в.

Когда процедура С завершается, В снова активизируется и стек возвращается в прежнее состояние (см. рис. 4.7, б), так что LV теперь указывает на локальные переменные процедуры В. Когда процедура В завершается, стек переходит в исходное состояние (см. рис. 4.7, а). При любых условиях LV указывает на базовый адрес стекового фрейма локальных переменных текущей процедуры, a SP — на верхнее слово этого фрейма.

Предположим, что процедура А вызывает процедуру D которая содержит 5 локальных переменных. Соответствующий стек показан на рис. 4.7, г. Локальные переменные процедуры D используют область памяти процедуры В и часть стека процедуры С. В памяти с такой организацией размещаются только текущие процедуры. Когда процедура завершается, отведенный для нее участок памяти освобождается.

Но стек используется для хранения не только локальных переменных, но и операндов во время вычисления арифметических выражений. Такой стек называется стеком операндов. Предположим, что перед вызовом процедуры В процедура Л должна произвести следующее вычисление:

а1 = а2 + аЗ.

Чтобы вычислить эту сумму, можно поместить а2 в стек, как показано на рис. 4.8, а. Тогда значение регистра SP увеличится на число, равное количеству байтов в слове (скажем, на 4), и будет указывать на адрес первого операнда. Затем в стек помещается переменная аЗ, как показано на рис. 4.8, б. Отметим, что названия процедур и переменных выбираются пользователем, а названия регистров и кодов операций жестко определены, поэтому названия процедур и переменных мы выделяем в тексте курсивом.

Теперь можно произвести вычисления, выполнив команду, которая выталкивает два слова из стека, складывает их и помещает результат обратно в стек, как показано на рис. 4.8, в. После этого верхнее слово можно вытолкнуть из стека и поместить его в локальную переменную al, как показано на рис. 4.8, г.

Фреймы локальных переменных и стеки операндов могут смешиваться. Например, когда вызывается функция ? при вычислении выражения х2 + ?(х), часть этого выражения 2) может находиться в стеке операндов. Результат вычисления функции остается в стеке над х2, чтобы следующая команда могла сложить операнды.

Следует упомянуть, что все машины используют стек для хранения локальных переменных, но не все используют его для хранения операндов. В большинстве машин нет стека операндов, но и у JVM, и у IJVM он есть.