- •Введение
- •1. Особенности персонального компьютера
- •1.1. Оперативная память
- •1.2. Регистры
- •1.3. Представление данных
- •1.4. Представление команд
- •2. Язык ассемблера. Начальные сведения
- •2.1. Лексемы
- •2.2. Предложения
- •2.3. Директивы определения данных
- •2.4. Директивы эквивалентности и присваивания
- •2.5. Выражения
- •3. Пересылки. Арифметические команды
- •3.1. Обозначения операторов команд
- •3.2. Команды пересылки
- •3.3. Команды сложения и вычитания
- •3.4. Команды умножения и деления
- •3.5. Изменение размера числа
- •3.6. Примеры
- •3.7. Лабораторная работа № 1
- •4. Переходы. Циклы
- •4.1. Безусловный переход
- •4.2. Команды сравнения и условного перехода
- •4.3. Команды управления циклом
- •4.4. Вспомогательные операции ввода-вывода
- •4.5. Массивы
- •4.6. Лабораторная работа № 2
- •5. Программные сегменты
- •5.1. Сегментирование адресов в пк
- •5.2. Программные сегменты
- •5.3. Начальная загрузка сегментных регистров
- •5.4. Структура программы
- •6. Стек
- •6.1. Стек и сегмент стека
- •6.2. Стековые команды
- •6.3. Приемы работы со стеком
- •7. Процедуры
- •7.1. Дальние переходы
- •7.2. Подпрограммы-процедуры
- •7.3. Передача параметров через регистры
- •7.4. Передача параметров через стек
- •7.5. Локальные данные процедур
- •7.6. Лабораторная работа № 3
- •8. Ввод и вывод данных
- •8.1. Реализация основных операций ввода-вывода
- •8.2. Операции ввода-вывода
- •8.3. Пример структуры программы
- •Заключение
- •Библиографический список
- •Оглавление
- •Учебное издание
- •394026 Воронеж, Московский просп., 14
7.4. Передача параметров через стек
Передача параметров через регистры – вещь удобная и используется очень часто. Однако делать так можно, только если параметров немного; если же параметров много, то на них попросту не хватит регистров. В таком случае используют иной способ передачи параметров – через стек: основная программа записывает фактические параметры (их значения или адреса) в стек, а процедура затем их оттуда извлекает. Конкретно реализовать эту идею можно по-разному, здесь же будет рассмотрен тот способ передачи параметров через стек, который используется транслятором языка Турбо Паскаль.
Пусть процедура Р имеет к параметров: Р(а1,а2,...,аk). Договоримся, что перед обращением к процедуре основная программа записывает параметры в стек. В каком порядке? Это решает автор программы. Будем считать, что параметры записываются в стек слева направо: сначала записывается 1-й параметр, затем 2-й и т. д. Если для определенности договориться, что каждый параметр имеет размер слова и его не надо вычислять (эти ограничения легко обходятся), тогда команды основной программы, реализующие обращение к процедуре, будут следующими (на рис. 37 показано состояние стека после выполнения этих команд, т. е. на входе в процедуру):
;обращение P(a1,a2…,ak)
PUSH a1
PUSH a2
…
PUSH ak
CALL P
(AB) …
Рис. 37. Состояние стека после вызова подпрограммы
Теперь начинает работать процедура. И сразу возникает проблема: как ей добраться до параметров? Это проблема доступа к элементам стека без считывания их из стека. Как она решается, уже известно: надо воспользоваться регистром ВР, т. е. заслать в него адрес вершины стека (содержимое регистра SP), а затем использовать выражения вида [BP+i] для доступа к параметрам процедуры. Однако здесь есть одно «но»: мы портим регистр ВР, а он может использоваться в основной программе. Поэтому сначала надо спасти прежнее значение этого регистра и только затем уж пересылать в него значение регистра SP. Таким образом, выполнение процедуры должно начинаться со следующих команд, которые называют «входными» действиями процедуры (на рис. 38 изображено состояние стека после этих команд; для определенности считается, что процедура близкая, поэтому адрес возврата занимает одно слово в стеке):
; «входные» действия процедуры
P
SP,BP->
PUSH BP ;спасти BP
MOV BP,SP ;BP – на вершину стека
...
команды процедуры
Рис. 38. Состояние стека после выполнения входных действий подпрограммы
Как видно из рисунка, после записи в стек старого значения ВР (ВРст) для доступа к параметрам процедуры надо использовать следующие выражения: [ВР+4] – для доступа к последнему параметру, [ВР+6] – для доступа к предпоследнему параметру и т. д. Например, считывание последнего параметра в регистр АХ (АХ:=ак) осуществляется командой MOV АХ, [ВР+4].
Далее идут команды собственно процедуры. После их завершения процедура обязана выполнить действия, которые называются «выходными». Рассмотрим их. Прежде всего следует отметить, что к этому моменту стек должен быть в том же состоянии, в каком он был после «входных» действий. (Если это не так, то восстановить данное состояние можно командой MOV SP,BP.) Тогда к концу работы процедуры в вершине стека будет находиться старое значение регистра ВР. Считываем его и восстанавливаем ВР. Теперь в вершине стека окажется адрес возврата. Казалось бы, можно выполнить команду RET и уйти из процедуры, однако это не так – надо еще очистить стек от параметров, которые уже не нужны. Кто должен делать эту очистку – процедура или основная программа? Конечно, это может сделать основная программа, для чего в ней после команды CALL P надо выполнить команду ADD SP, 2*k. Однако лучше, если очистку стека будет делать сама процедура. Дело в том, что обращений к процедуре много, поэтому в основной программе команду ADD придется выписывать многократно, а процедура – одна, поэтому в ней такую команду надо выписать только раз.
Это общее правило: если что-то может сделать и основная программа, и процедура, то лучше, если это «что-то» будет делать процедура. Так меньше команд получается.
Итак, процедура должна сначала очистить стек от параметров и только затем передать управление по адресу возврата. Чтобы упростить реализацию этой пары действий, в систему команд ПК введен расширенный вариант команды RET – с непосредственным операндом, который трактуется как число без знака:
RET i16
По этой команде сначала из стека удаляется адрес возврата, затем стек очищается на указанное операндом число байтов и далее выполняется переход по адресу возврата:
стек -> IP, [стек -> CS,] SP:=SP+i16
Действие «стек -> CS» выполняется лишь при дальнем возврате.
Следует сделать несколько замечаний. Во-первых, команда RET – это на самом деле команда RET 0, т. е. возврат без очистки стека. Во-вторых, операнд команды указывает, на сколько байтов, а не слов надо очищать стек, поэтому для очистки стека от k параметров, каждый из которых имеет размер слова, надо указывать операнд 2*k, а не k. В-третьих, в операнде не должен учитываться адрес возврата – команда RET считывает его до очистки стека.
С учетом всего сказанного «выходные» действия процедуры таковы:
;«выходные» действия процедуры
POP ВР ;восстановить старое значение ВР
RЕТ 2*k ;очистка стека от к параметров-слов
;и возврат
P ENDP
После такого возврата из процедуры состояние стека будет тем же самым, каким оно было до выполнения команд обращения к процедуре, т. е. до выполнения команд записи параметров в стек. Тем самым в стеке уничтожаются все следы обращения к процедуре, а это как раз то, что надо.
Такова общая схема передачи параметров через стек. Еще раз следует напомнить, что этот способ передачи параметров универсален, его можно использовать при любом числе параметров. Однако этот способ более сложный, чем передача параметров через регистры, поэтому, если можно, следует передавать параметры через регистры, так будет проще и короче. Что же касается результата процедуры, то; он крайне редко передается через стек и обычно передается через регистр.
В качестве конкретного примера опишем процедуру NULL(A,N), которая обнуляет N байтов памяти (в сегменте данных) начиная с адреса А, при условии, что адрес первого параметра (А) и значение второго параметра (N) передаются через стек. Сначала приведено описание процедуры, а затем показана реализация обращения NULL(X,100), по которому обнуляется 100-байтовый массив X:
X DB 100 DUP(?)
...
;вызов NULL(X,100)
LEA AX, X
PUSH AX ;адр.Х->стек
MOV AX, 100
PUSH AX ;100->стек
CALL NULL
...
NULL PROC
PUSH BP ;«входные» действия
MOV BP, SP
PUSH BX ;спасти ВХ и СХ
PUSH СХ ;(«над» ВРст)
MOV CX,[BP+4] ;CX=X
MOV BX,[BP+6] ;BX=N A
NULL1: MOV BYTE PTR [BX], 0 ;Обнуление
INC BX
LOOP NULL1
POP СХ ;восстановление СХ и ВХ
POP BX
POP ВР ;«выходные» действия
RET 4
NULL ENDP