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

7.3. Передача параметров через регистры

Теперь следует рассмотреть проблемы, связанные с параметрами процедур.

В языках высокого уровня для того, чтобы задать фактические параметры для процедуры, достаточно лишь выписать их в операторе вызова процедуры. В языке ассемблера проблема задания параметров решается не столь просто, поэтому стоит рассмотреть ее подробно.

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

Передавать фактические параметры процедуре можно по-разному. Простейший способ – передавать параметры через регистры: основная программа записывает фактические параметры в какие-то регистры, а процедура затем берет их оттуда и использует в своей работе. Аналогично можно поступить и с результатом, если он имеется: процедура записывает свой результат в какой-то регистр, а основная программа затем извлекает его оттуда. Через какие регистры передавать параметры и результат? Это личное дело автора программы, он сам определяет эти регистры.

Передача параметров по значению

Рассмотрим такой пример. Пусть надо вычислить c=max(a,b)+max(5,a-1), где все числа – знаковые и размером в слово. Вычисление mах(х,у) опишем как процедуру-функцию, при этом договоримся о следующем: первый параметр (x) ос­новная программа должна передавать через регистр АХ, второй параметр (y) – через регистр ВХ, а результат (max) процедура должна возвращать через регистр АХ. При этих условиях процедура и соответствующий фрагмент основной программы выглядят так (для примера опишем процедуру как дальнюю):

;процедура:

АХ=mах(АХ, ВХ)

MAX PROC FAR

CMP AX, BX

JGE MAX1

MOV AX, BX

МАХ1: RET

MAX ENDP

;основная программа

...

MOV AX,А ;АХ:=а

MOV BX,B ;BX:=b

CALL MAX ;AX:=max(a,b)

MOV С, АХ ;спасти АХ

MOV AX, 5 ;АХ:=5

MOV ВХ, А

DEC ВХ ;ВХ:=а-1

CALL MAX ;АХ:=mах(5,а-1)

ADD С, АХ ;С:=mах(а,b)+mах(5,а-1)

...

Если воспользоваться терминологией языков программирования высокого уровня, то в этом примере параметры передаются по значению: перед обращением к процедуре основная процедура вычисляет значения фактических параметров и именно эти значения записывает в регистры. Теперь же рассмотрим другой способ передачи параметров – по ссылке.

Передача параметров по ссылке

Возьмем следующую процедуру на языке Паскаль:

procedure D(var x:integer); begin x:=x div 16 end;

Пусть в программе есть такие обращения к ней: D(A) и D(B), где А и В – имена переменных, значениями которых являются неотрицательные числа.

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

Как передавать адрес? Через регистр: основная программа записывает в какой-то регистр адрес фактической переменной, а процедура берет его оттуда. Какой это регистр? Вообще говоря, любой, но лучше, если это будет регистр-модификатор, т. е. ВХ, ВР, SI или DI, т. к. процедуре придется модифицировать по этому регистру.

Пусть для данной процедуры D выбран регистр ВХ. Это означает, что к началу ее выполнения в регистре ВХ будет находиться адрес той ячейки (А или В), содержимое которой она обязана изменить. В подобной ситуации добраться до этой ячейки, как уже известно (это пример на косвенную ссылку), можно с помощью конструкции [ВХ].

С учетом всего сказанного получается следующий фрагмент основной программы, соответствующий обращениям D(A) и D(B), и следующую процедуру D (на строки с командами PUSH и POP пока не обращать внимания):

;основная программа

...

LEA ВХ,А ;ВХ=адрес A

CALL D ;D(A)

LEA BX,B ;ВХ=адрес В

CALL D ;D(B)

...

;процедура: ВХ=адрес х, х:=х div 16

D PROC

PUSH СХ ;спасти СХ

MOV CL, 4

SHR WORD PTR [BX], CL ;x:=x div 16

POP СХ ;восстановить СХ

RET

D ENDP

Сохранение регистров в процедуре

Как видно, процедуре D потребовался регистр CL, в который она заносит величину сдвига. Возникает вопрос: имеет ли право процедура менять, портить этот регистр?

Это очень важная проблема. Дело в том, что в ПК не так уж и много регистров и в то же время чуть ли не в каждой команде используется тот или иной регистр. Поэтому с большой вероятностью основной программе и процедуре могут потребоваться для работы одни и те же регистры, и тем самым они будут «мешать» друг другу. Конечно, можно договориться, чтобы основная программа и процедура пользовались разными регистрами, однако сделать это очень сложно – уж очень мало регистров в ПК. Поэтому обычно поступают иначе: разрешают и основной программе, и процедуре пользоваться одними и теми же регистрами, но при этом требуют от процедуры, чтобы она сохраняла те значения регистров, которые в них записала основная программа. Достичь этого просто: в начале своей работы процедура должна спасти в стеке значения тех регистров, которые ей потребуются для работы, после чего она может использовать эти регистры как угодно, а перед выходом она должна восстановить прежние значения этих регистров, считав их из стека. Именно так мы и поступили в процедуре D. Правда, здесь есть одна тонкость: нам надо сохранить значение байтового регистра CL, но, как уже известно, в стек можно записывать только слова. Поэтому спасаем в стеке не регистр CL, а весь регистр СХ и в конце также восстанавливаем весь регистр СХ.

Такое сохранение регистров настоятельно рекомендуется делать в любой процедуре, даже если явно видно, что основная программа не пользуется теми же регистрами, что и процедура. Дело в том, что исходный текст программы в дальнейшем может измениться (а это происходит очень часто), и может оказаться так, что после этих изменений основной программе потребуются эти регистры. И хорошо, если мы при этом вспомним, что надо подправить процедуру. Чаще же всего про это забывают, и потому основная программа и процедура начинают мешать друг другу. Поэтому лучше сразу предусмотреть в процедуре сохранение регистров, тогда при любых изменениях основная программа может не волноваться за свои регистры.

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

Передача параметров сложных типов

Теперь следует рассмотреть еще один случай передачи параметра по ссылке – когда параметром является данные сложного типа (массив, структура и т. п.). Даже если процедура не меняет это данное, ей все равно обычно передается не само данное (для него может просто не хватить регистров), а его начальный адрес. Зная этот адрес, процедура может легко добраться до любой части этого данного.

Рассмотрим следующий пример. Пусть имеются массивы из чисел без знака

X DB 100 DUP (?)

Y DB 25 DUP (?)

и требуется записать в регистр DL сумму максимальных элементов этих массивов:

DL=max(X[i]) + max(Y[i])

Поскольку здесь дважды приходится находить максимальный элемент массива, то, чтобы не повторяться, имеет смысл описать это действие в виде процедуры. Назовем ее МАХ и опишем ее при условии, что начальный адрес массива передается процедуре через регистр ВХ, количество элементов в массиве – через регистр СХ, а свой результат процедура возвращает через регистр AL. При этих соглашениях описание процедуры и фрагмент основной программы, решающий поставленную задачу, выглядят так:

;процедура MAX:AL=max(W[0..N-1]),

;где ВХ=нач.адрес W,CX=N

MAX PROC

PUSH BX ;спасти регистры,

PUSH СХ ;используемые в процедуре

MOV AL, 0 ;AL=0(нач. значение максимума)

МАХ1:CMP [BX], AL

JLE MAX2 ;W[i]>AL ==> AL:=W[i]

MOV AL, [BX]

MAX2:INC BX

LOOP MAX1

POP СХ ;восстановить регистры

POP BX

RET ;выход из процедуры

MAX ENDP

...

;фрагмент основной программы для вычисления

;DL=max(X[i])+max(Y[i])

LEA BХ, Х ;ВХ=нач.адрес массива X

MOV СХ, 100 ;СХ=число элементов в X

CALL MAX ;AL=max(X[i])

MOV DL, AL ;спасти AL

LEA BX, Y ;ВХ=нач.адрес массива Y

MOV CX, 25 ;СХ=число элементов в Y

CALL MAX ;AL=max(Y[i])

ADD DL,AL

...