4.2. Операции со стеком

В гл.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. Здесь длина программы - всего два байта, и к тому же не требуется промежуточный регистр. Однако эти две команды занимают больше времени, так как нужны дополнительные циклы чтения и записи в стек. Это - метод потери в скорости выполнения ради уменьшения размера объектного кода.