Смекни!
smekni.com

ЯЗЫК МАКРОАССЕМБЛЕРА IBM PC (стр. 8 из 9)

MOV AX,DS

MOV ES,AX ;ES:=DS

LEA SI,A ;ES:SI - "откуда"

LEA DI,B ;DS:DI - "куда"

REP MOVSB ;пересылка CX байтов

1.7. СТЕК. ПОДПРОГРАММЫ.

1.7.1 Стек

В ПК имеются специальные команды работы со стеком, т.е. областью памяти, доступ к элементам которой осуществляется по принципу "послед­ним записан - первым считан". Но для того, чтобы можно было воспользо­ваться этими командами, необходимо соблюдение ряда условий.

Под стек можно отвести область в любом месте памяти. Размер ее мо­жет быть любым, но не должен превосходить 64Кб, а ее начальный адрес должен быть кратным 16. Другими словами, эта область должна быть сег­ментом памяти; он называется сегментом стека. Начало этого сегмента (первые 16 битов начального адреса) должно обязательно храниться в сегментном регистре SS.

Хранимые в стеке элементы могут иметь любой размер, однако следует учитывать, что в ПК имеются команды записи в стек и чтения из него только слов. Поэтому для записи байта в стек его надо предварительно расширить до слова, а запись или чтение двойных слов осуществляются парой команд.

В ПК принято заполнять стек снизу вверх, от больших адресов к меньшим: первый элемент записывается в конец области, отведенной под стек, второй элемент - в предыдущую ячейку области и т.д. Считывается всегда элемент, записанный в стек последним. В связи с этим нижняя граница стека всегда фиксирована, а верхняя - меняется. Слово памяти, в котором находится элемент стека, записанный последним, называется вершиной стека. Адрес вершины, отсчитанный от начала сегмента стека, обязан находиться в указателе стека - регистре SP. Таким образом, аб­солютный адрес вершины стека определяется парой SS:SP.

----- ----- -----

SS:SP | | SS:SP | | SS:SP | |

| ----- запись | ----- чтение | -----

| | | =======> ---->| b | =======> | | |

| ----- в стек ----- из стека | -----

----->| a | | a | ---->| a |

----- ----- -----

Значение 0 в регистре SP свидетельствует о том, что стек полностью заполнен (его вершина "дошла" до начала области стека). Поэтому для

контроля за переполнением стека надо перед новой записью в стек прове­рять условие SP=0 (сам ПК этого не делает). Для пустого стека значение SP должно равняться размеру стека, т.е. пара SS:SP должна указывать на байт, следующий за последним байтом области стека. Контроль за чтением из пустого стека, если надо, обязана делать сама программа.

Начальная установка регистров SS и SP может быть произведена в са­мой программе, однако в MASM предусмотрена возможность автоматической загрузки этих регистров. Если в директиве SEGMENT, начинающей описание сегмента стека, указать параметр STACK, тогда ассемблер (точнее, за­грузчик) перед тем, как передать управление на первую команду машинной программы, загрузит в регистры SS и SP нужные значения. Например, если в программе сегмент стека описан следующим образом:

ST SEGMENT STACK

DB 256 DUP(?) ;размер стека - 256 байтов

ST ENDS

и если под этот сегмент была выделена область памяти начиная с абсо­лютного адреса 12340h, тогда к началу выполнения программы в регистре SS окажется величина 1234h, а в регистре SP - величина 100h (=256). Отметим, что эти значения соответствуют пустому стеку.

1.7.2 Основные стековые команды

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

Запись слова в стек: PUSH op

Здесь op обозначает любой 16-битовый регистр (в том числе и сегмент­ный) или адрес слова памяти. По этой команде значение регистра SP уменьшается на 2 (вычитание происходит по модулю 2^16), после чего указанное операндом слово записывается в cтек по адресу SS:SP.

Чтение слова из стека: POP op

Слово, считанное из вершины стека, присваивается операнду op (регист­ру, в том числе сегментному, но не CS, или слову памяти), после чего значение SP увеличивается на 2.

Переход с возвратом: CALL op

Эта команда записывает адрес следующей за ней команды в стек и затем делает переход по адресу, определяемому операндом op. Она используется для переходов на подпрограммы с запоминанием в стеке адреса возврата.

Имеются следующие разновидности этой команды (они аналогичны вари-

антам команды безусловного перехода JMP):

- внутрисегментный относительный длинный переход (op - непосредст­венный операнд размером в слово, а в MASM - это метка из текущего сег­мента команд или имя близкой процедуры (см. ниже)); в этом случае в стек заносится только текущее значение счетчика команд IP, т.е. смеще­ние следующей команды;

- внутрисегментный абсолютный косвенный переход (op - адрес слова памяти, в которой находится адрес (смещение) той команды, на которую и будет сделан переход); и здесь в стек записывается только смещение ад­реса возврата;

- межсегментный абсолютный прямой переход (op - непосредственный операнд вида seg:ofs, а в MASM - это FAR PTR <метка> или имя дальней процедуры (см. ниже)); здесь в стек заносится текущие значение регист­ров CS и IP (первым в стек записывается содержимое CS), т.е. абсолют­ный адрес возврата, после чего меняются регистры CS и IP;

- межсегментный абсолютный косвенный переход (op - адрес двойного слова, в котором находится пара seg:ofs, задающая абсолютный адрес пе­рехода); и здесь в стеке спасается содержимое регистров CS и IP.

Переход (возврат) по адресу из стека: RET op

Из стека считывается адрес и по нему производится переход. Если указан операнд (а это должно быть неотрицательное число), то после чтения ад­реса стек еще очищается на это число байтов (к SP добавляется это чис­ло). Команда используется для возврата из подпрограммы по адресу, за­писанному в стек по команде CALL при вызове подпрограммы, и одновре­менной очистки стека от параметров, которые основная программа занесла

в стек перед обращением к подпрограмме.

Команда RET имеет две разновидности (хотя в MASM они записываются и одинаково): в одном случае из стека считывается только одно слово ­смещение адреса возврата, а во втором - из стека считывается пара seg: ofs, указывающая абсолютный адрес возврата. Как ассемблер определяет, какой из этих двух случаев имеет место, объяснено ниже.

В ПК стек в основном используется для организации подпрограмм и прерываний. Подпрограммы рассматриваются ниже, а прерывания - в главе 3. Однако, даже если программе не нужен стек, она все равно должна от­вести под него место. Дело в том, что стеком будет неявно пользоваться операционная система при обработке прерываний, которые возникают (нап­ример, при нажатии клавиш на клавиатуре) в то время, когда выполняется программа. Для нужд ОС рекомендуется выделять в стеке 64 байта.

1.7.3 Подпрограммы

Типичная схема огранизации подпрограмм, обычно используемая транс­ляторами с языков высокого уровня для реализации процедур и функций (в частности, рекурсивных), следующая.

При обращении к подпрограмме в стек заносятся параметры для нее и адрес возрата, после чего делается переход на ее начало:

PUSH param1 ;запись 1-го параметра в стек

...

PUSH paramk ;запись последнего (k-го) параметра в стек

CALL subr ;переход в возратом на подпрограмму

(Замечание: если необходимо вычислить параметр или если его размер от­личен от слова, тогда для записи параметра в стек нужно, конечно, нес­колько команд, а не одна.) Состояние стека после выполнения этих ко­манд обращения к подпрограмме показано на рис. a

| | |--------------|

| | | лок.величины |<-SP

| | -2| (m байтов) |

| | |--------------|

| | 0| BP стар |<-BP

|адрес возврата|<-SP +2|адрес возврата|

| 1-й параметр | +4| 1-й параметр |

| ... | | ... |

| k-й параметр | | k-й параметр |

|//////////////| |//////////////|

|//////////////|<-BP |//////////////|

рис. а рис. б

Первыми командами подпрограммы обычно являются следующие:

PUSH BP ;спасти в стеке старое значение BP

MOV SP,BP ;установить BP на вершину стека

SUB SP,m ;отвести в стеке место (m байтов) под локальные

;величины подпрограммы (состояние стека в этот ;момент показано на рис. б)

Поясним эти "входные" команды. В подпрограмме для обращения к ячейкам стека, занятых параметрами, используется (как базовый) регистр BP: если в BP занести адрес вершины стека, то для доступа к этим ячей­кам следует использовать адресные выражения вида i[BP] или, что то же самое, [BP+i]. (Отметим, что применять здесь регистры-модификаторы BX, SI и DI нельзя, т.к. формируемые по ним исполнительные адреса будут сегментироваться по умолчанию по регистру DS, а в данном случае нужно сегментирование по SS.) Однако данная подпрограмма может быть вызвана из другой, также использующей регистр BP, поэтому прежде, чем устано­вить BP на вершину стека, надо спасти в стеке старое значение этого регистра, что и делает первая из "входных" команд. Вторая же команда устанавливает BP на вершину стека. Если предположить, что каждый пара­метр и адрес возврата занимают по слову памяти, тогда доступ к первому параметру обеспечивается адресным выражением [BP+4], ко второму - вы­ражением [BP+6] и т.д. (см. рис. б).

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

Такое место обычно отводится в стеке (а для рекурсивных подпрограмм ­только в стеке) "над" ячейкой, занимаемой старым значением BP. Если под эти величины нужно m байтов, то такой "захват" места можно реали­зовать простым уменьшением значения регистра SP на m, что и делает 3-я "входная" команда. Доступ к локальным величинам обеспечивается адрес­ными выражениями вида [BP-i]. Если подпрограмме не нужно место под ло­кальные величины, тогда третью из "входных" команд следует опустить.

Выход из подпрограммы реализуется следующими командами:

MOV SP,BP ;очистить стек от локальных величин

POP BP ;восстановить старое значение BP

RET 2*k ;возврат из подпрограммы и очистка стека от

;параметров (считаем, что они занимают 2*k байтов) Первая из этих "выходных" команд заносит в регистр SP адрес той ячейки стека, где хранится старое значение регистра BP, т.е. происходит очис­тка стека от локальных величин (если их не было, то данную команду надо опустить). Вторая команда восстанавливает в BP это старое значе­ние, одновременно удаляя его из стека. В этот момент состояние стека будет таким же, как и перед входом в подпрограмму (см. рис а). Третья команда считывает из стека адрес возврата (в результате чего SP "опус­кается" на 2 байта), затем добавляет к SP число, которое должно рав­няться числу байтов, занимаемых всеми параметрами подпрограммы, и за­тем осуществляет переход по адресу возврата. В этот момент состояние стека будет таким же, каким оно было перед обращением к подпрограмме.