Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Учебное пособие 3000239.doc
Скачиваний:
23
Добавлен:
30.04.2022
Размер:
1.12 Mб
Скачать

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->

PROC

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