В гл.3 обсуждалось, как реализован стек в микропроцессоре 8088.
Микропроцессор 8088 адресует стек с помощью регистровой пары SS:SP.
Помещение объектов в стек приводит к тому, что он растет в сторону
меньших адресов памяти. Стек, кроме всего прочего, служит и для
запоминания адресов возврата из подпрограмм. В этом разделе
рассматриваются некоторые команды, которые непосредственно работают
со стеком.
Фиг.4.7 иллюстрирует ассемблированные стековые команды.
Мнемоника команд очевидна; за кодами операций PUSH и POP следует
имя регистра для указания операнда. Единственным исключением
является помещение и извлечение из стека регистра флагов, которые
используют мнемонику PUSHF и POPF соответственно. Содержимое любой
ячейки памяти, которую программа может адресовать, используя
возможные способы адресации, также может быть помещено или
извлечено из стека.
При любых действиях со стеком в микропроцессоре 8088 базовой
единицей информации является 16=битовое слово. Длина любого
объекта, помещаемого в стек либо извлекаемого из стека, составляет
одно или несколько слов. Байтовых команд, связанных с засылкой
данных или извлечением их из стека, не существует. Если, например,
программе необходимо сохранить содержимое регистра AL а стеке, она
должна поместить содержимое регистра AX, так как не существует
способа сохранения только содержимого регистра AL.
Основное назначение стека - временное хранение информации. Как
мы уже видели, стек используется для сохранения адреса возврата;
программа также может сохранять данные. Если программа хочет
использовать регистр, пусть даже сохранить текущие данные, она
может послать значение этого регистра в стек. Эти данные
сохраняются в стеке и позже могут быть восстановлены. Например,
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:00:43
Фиг. 4.7 Операции со стеком Page 1-1
PAGE ,132
TITLE Фиг. 4.7 Операции со стеком
0000 CODE SEGMENT
ASSUME CS:CODE,DS:CODE
0000 EXWORD LABEL WORD
0000 50 PUSH AX ; Поместить регистр в стек
0001 56 PUSH SI
0002 0E PUSH CS ; Можно поместить в стек сегментный регистр
0003 FF 36 0000 R PUSH EXWORD ; Можно также поместить в стек ячейку памяти
0007 8F 06 0000 R POP EXWORD ; Можно извлечь то, что в помещено в стек
000B 07 POP ES ; Можно извлечь в другое место
000C 5F POP DI
000D 5B POP BX
000E 9C PUSHF ; Другая мнемоника для флагов
000F 9D POPF
;----- Пример, демонстрирующий передачу параметров
0010 50 PUSH AX
0011 53 PUSH BX
0012 51 PUSH CX
0013 52 PUSH DX
0014 E8 0017 R CALL SUBROUTINE ; Передача управления
; ... ; Продолжение программы
0017 SUBROUTINE PROC NEAR
0017 8B EC MOV BP, SP ; Занесение в BP адреса стека
0019 8B 46 02 MOV AX, [BP+2] ; Выборка последнего параметра (DX)
001C 8B 5E 04 MOV BX, [BP+4] ; Выборка третьего параметра (CX)
001F 8B 4E 06 MOV CX, [BP+6] ; Выборка второго параметра (BX)
0022 8B 56 08 MOV DX, [BP+8] ; Выборка первого параметра (AX)
; ...
0025 C2 0008 RET 8 ; Возврат с уничтожением поля параметров
0028 SUBROUTINE ENDP
0028 CODE ENDS
END
Фиг. 4.7 Операции со стеком
программе нужно ввести код из порта ввода=вывода 3DAH, а в регистре
DX находятся важные данные. Следующая последовательность команд
PUSH DX
MOV DX, 3DAH
IN AL, DX
POP DX
сохраняет регистр DX в стеке на то время, пока он нужен в
программе для выполнения команды IN.
Операции сохранения регистров в стеке обычно используется в
начале программы. В большинстве случаев подпрограмма старается
избегать изменения содержимого любого регистра. Поэтому
подпрограмма, которой нужны регистры для вычислений и для хранения
адресов, помещает все необходимые ей регистры в стек до выполнения
команд обработки. Затем, после выполнения, подпрограмма
восстанавливает регистры из стека с помощью команд POP.
Помните о том, что стек - это структура типа LIFO. Если в вашей
программе выполняется последовательность команд
PUSH BX
PUSH CX
POP BX
POP CX
то результирующим эффектом будет обмен значений в регистрах BX
и CX. Только тот факт, что в команде PUSH был указан регистр BX, не
означает, что команда POP, указывающая на тот же регистр,
восстанавливает первоначальное содержимое регистра BX. Еще одним
важным моментом является то, что команды PUSH и POP должны быть
сбалансированы, т.е. каждой команде PUSH должна соответствовать
команда POP. Точно так же, как и в случае скобок в арифметическом
выражении, если посылки и извлечения из стека не сбалансированы,
результаты будут неверны. Более того, несбалансированные команды
PUSH/POP обычно приводят к возврату из подпрограмм по адресу
значения данных, а не значения указателя команд из=за того, что
микропроцессор 8088 записывает в стек адрес возврата. Обычно это
вынуждает микропроцессор выполнять программу, которую программист
никогда не писал. Поэтому баланс стековых команд обязателен. Будьте
особенно внимательны в тех случаях, когда в программе есть условный
переход вокруг стековых операций; можно легко выпустить из виду
один из вариантов выполнения, что оставит стек несбалансированным.
Наряду с сохранением данных, программа может использовать стек
в качестве буфера при некоторых пересылках; в частности, не
существует команды пересылки, которая бы переносила данные из
одного сегментного регистра в другой. В обычном случае загрузка
одного сегментного регистра из другого требует сначала загрузки его
значения а промежуточный регистр. Это достигается следующей
последовательностью из двух команд:
MOV AX,CS ;переслать значение регистра
;CS в регистр AX
MOV DS,AX ;загрузить это значение в
; регистр DS
Каждая из этих команд имеет длину несколько байт, и эта
последовательность разрушает содержимое регистра AX. Альтернативным
подходом может быть
PUSH CS ; регистр CS поместить в стек
POP DS ; поместить это значение в регистр DS
Результирующий эффект этой последовательности команд тот же,
регистр DS загружается из регистра CS. Здесь длина программы -
всего два байта, и к тому же не требуется промежуточный регистр.
Однако эти две команды занимают больше времени, так как нужны
дополнительные циклы чтения и записи в стек. Это - метод потери в
скорости выполнения ради уменьшения размера объектного кода.