Главная страница «Первого сентября»Главная страница журнала «Информатика»Содержание №4/2008


В мир информатики
Эксперименты

Стек

Продолжение. Начало см. “В мир информатики” № 102–103 (“Информатика” № 2–3/2008)

В пункте 1.1 (в первой части статьи) при изложении базовых принципов работы стека было показано, что стек можно организовать программно. Например, если условно принять регистр SI за указатель программно организуемого стека, то полным функциональным эквивалентом для команды PUSH AX станет последовательность инструкций:

DEC SI

DEC SI

MOV [SI],AX

а для команды POP AX:

MOV AX,[SI]

INC SI

INC SI

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

Тем не менее гораздо более удобным является аппаратная реализация стека. В этом случае выделяется специальный стековый регистр (как уже указывалось, в IBM-совестимых и многих других компьютерах его обозначают SP), с которым и работают по умолчанию команды PUSH и POP.

Примечание. Строго говоря, для получения полного значения указателя стека в процессорах Intel еще используется дополнительный сегментный регистр стека SS. Мы не будем его рассматривать по следующим причинам. Во-первых, сегментация свойственна не только адресации стековой информации, но и самой программе, а также ее данным. Так что это не есть особенность стека, который мы изучаем. Во-вторых, сегментный метод адресации сейчас уже безнадежно устарел: он обеспечивает 20-разрядный адрес, тогда как современные процессоры постепенно переходят от 32-разрядной архитектуры к 64-разрядной.

Наконец, в RISC-процессорах 1 существует еще одна весьма своеобразная и интересная аппаратная реализация некоторой разновидности стека. Ее часто называют регистровым окном. Основная идея состоит в том, что в любой момент времени процессор “видит” только одно регистровое окно, причем регистры в нем всегда пронумерованы единообразно [1]. Окно можно аппаратно переключать на другие регистры с помощью специального регистра CWP (Current Window Pointer — указателя текущего окна). Например, на рис. 3 показано, что при установке значения CWP = 24 (новое положение окна показано пунктиром) регистр R24 приобретает имя R0, R25 — R1 и т.д.

Регистровое окно делится на три области:

· область регистров параметров (R0–R7);

· область локальных регистров (R8–R23);

· область временных регистров (R24–R31).

Рис. 3

Важно подчеркнуть, что области параметров и временных регистров при переключении окон перекрываются, что дает возможность передавать данные из одного окна в другое. Например, в наших обозначениях код, занесенный при CWP = 0 в регистр R24, после переключения окна будет доступен под именем R0, а в R31 — в качестве R7. Зато регистры R0–R23 в результате переключения окажутся надежно сохранены, поскольку станут недоступны процессору.

Реальный RISC-процессор (например, процессор с архитектурой типа SPARC) устроен сложнее, но нам описанной картины вполне достаточно. Особо подчеркнем, что описанная процедура переключения регистрового окна во многом эквивалентна работе стека.

2. Эксперименты со стеком

Пример 1. Использование стека для временного хранения данных

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

Продемонстрируем основные приемы работы со стеком на примере регистров AX и BX, для чего в программе-отладчике Debug проведем эксперимент, зафиксированный в протоколе 1 2.

Начнем с установки начальных значений: придадим выбранным регистрам AX и BX произвольные, но легко узнаваемые значения 1111 и 2222, а указатель стека SP перенесем с конца выделенного нам операционной системой сегмента на адрес 170. Цель последнего действия — сделать изменения в стековой области памяти удобно наблюдаемыми.

Проверив (командой -r), что значения регистров AX, BX и SP установлены нужным образом (как всегда, характерные места протокола, на которые следует обязательно обратить внимание, подчеркнуты!), наберем некоторую несложную программу. Она абсолютно демонстрационная и лишена какого-либо функционального смысла: сначала двумя командами PUSH в стеке последовательно сохраняются значения AX и BX, затем они сознательно “портятся” и восстанавливаются командами POP (что очень важно, в обратном порядке!). Далее командой u проверяется правильность набора, а командой d мы убеждаемся, что в области адресов ниже 170 (в протоколе подчеркнута), отведенной под стек, пока никакой информации нет (во всяком случае, в моих экспериментах там при старте всегда оказывались нули).

Запускаем программу на исполнение командой t4, после чего внимательно изучаем результаты каждой из четырех выполненных команд. В первых двух стоит обратить внимание на уменьшение значения SP — идет запись в стек (его состояние мы просмотрим чуть позднее), а две последующих очевидным образом меняют содержимое регистров AX и BX на 3333 и 4444 соответственно.

Теперь самый интересный момент: используя команду d100, посмотрим, что находится в стековой области. Учтем, что она заполняется в сторону уменьшения адресов, так что в пару байтов с адреса 16E заносится наше значение 1111, а с адреса 16C — 2222. Нижележащие ячейки стека тоже изменились, но разговор об этом отложим на потом.

Итак, в стеке надежно сохранены значения регистров, причем регистр SP показывает на последнее из них.

По следующей команде t восстанавливается значение 2222 в регистре BX (обязательно обратите внимание на тот факт, что в стеке этого значения больше нет — в результате работы отладчика оно “стерлось”; в теории оно должно было сохраниться, но, поскольку данная ячейка стека освободилась, отладчик Debug нашел ей новое применение). Наконец, последняя команда POP восстановила первоначальное значение AX, и никакого воспоминания о промежуточных значениях 3333 и 4444 больше не сохранилось.

Таким образом, мы видели, как согласованные между собой пары команд PUSH и POP обеспечили кажущуюся(!) неприкосновенность наших “подопытных” регистров.

В заключение обсудим, каково происхождение дополнительной информации, которую мы наблюдали в стеке. Дело в том, что на самом деле, помимо нашей коротенькой тестовой программы, стеком пользуется сам Debug, что и порождает наблюдаемые изменения. Что именно сохраняет отладчик в стеке, нас не интересует; гораздо важнее обратить внимание на тот факт, что как только ячейка стека освободилась, ее содержимое использовать больше нельзя, так как оно может стать каким угодно. Из теоретических рассуждений кажется, что освободившееся значение продолжает по-прежнему лежать в стеке, но секрет в том, что стеком может воспользоваться некоторая “внешняя” (по отношению к нашей) программа. Отсюда следует важный вывод: сохранение в стековой памяти уже считанных значений не гарантируется!

Задания для самостоятельной работы

1. Убедитесь, что если команды POP BX и POP AX поменять местами, то мы получим простой алгоритм обмена значениями для двух регистров. Для проверки напишите программу обмена при посредстве стека содержимого регистров CX и DX и с помощью отладчика Debug проверьте, что она работает.

2. Измените нашу тестовую программу так, чтобы она сохраняла значения регистров AX, BX, CX и DX, а затем восстанавливала.

3. С помощью отладчика Debug разберитесь, как работает следующая последовательность команд:

MOV SP,172

PUSH AX

PUSH BX

MOV SP,170

POP BX

Литература

1. Столингс В. Структурная организация и архитектура компьютерных систем. М.: Издательский дом “Вильямс”, 2002.


1 RISC-процессоры — процессоры с ограниченной системой команд. — Прим. ред.

2 Напомним, что команды, выделенные в протоколе жирным шрифтом, набираются “вручную”.

Е.. А.. Еремин,
г. Пермь

TopList