Вызов процедур и возврат из них

Существуют специальные команды, упрощающие реализацию вызова процедуры и возврат из нее.

Команда вызова процедуры записывается следующим образом:

CALL < имя процедуры>

Команда записывает в стек адрес следующей за ней команды и осуществляет переход на заданную операндом процедуру.

Команда возврата из процедуры записывается следующим образом:

RET

Команда считывает адрес с вершины стека и переходит по нему. На момент ее выполнения стек должен быть точно в том состоянии, в каком он был в момент входа в процедуру (иначе адрес возврата не сможет быть считан). Существует также команда RET с параметром, которая сначала удаляет из стека указанное количество байтов, а затем уже считывает адрес возврата.

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

 

CALL FAR PTR P

 

Передача параметров и результата через регистры

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

Различают передачу параметра по значению (в регистр записывается значение параметра) и по ссылке (в регистр записывается адрес переменной-параметра). Как правило, по значению передают простые переменные, а по ссылке – данные сложных типов (массивы, структуры и т.п.).

Для загрузки адреса переменной в регистр используется команда LEA, например:

 

LEA AX, X

 

Данная команда очень похожа на команду MOV, но между ними имеется принципиальное различие: если MOV записывает в регистр содержимое ячейки памяти, адрес которой указан вторым операндом, то команда LEA записывает в регистр адрес, указанный во втором операнде.

Результат работы процедуры, как правило, возвращается через регистр (регистры).

Пример. Найти сумму максимальных элементов двух массивов байтов, рассматриваемых как целые без знака.

 

S SEGMENT STACK ;Сегмент стека

DB 200 DUP(0ABh)

S ENDS

 

D SEGMENT ; Сегмент данных

A DB 1, 100, 20, 40, 23

B DB 200, 100, 20

SUM DB ?

D ENDS

 

CODE SEGMENT

ASSUME SS:S, CS:Code, DS:D

; Процедура поиска максимума. Параметры передаются

; через BX (адрес массива) и CX (число элементов)

MAX PROC

PUSH BX ;Сохранение значений BX и CX в ; стек, так как они будут изменяться

; В BX будет записываться адрес текущего элемента

; массива, а в CX – счетчик цикла

PUSH CX

MOV AL,0 ; В AL будет храниться максимум

L1: CMP [BX], AL ; Начало цикла поиска максимума

JBE L2

MOV AL, [BX] ;Если новый максимум найден,

;то записываем его в AL

L2: INC BX ;Переход к следующему элементу

;массива

LOOP L1 ;Окончание тела цикла

 

POP CX ; Восстановление значений

POP BX ; регистров BX и CX

RET

MAX ENDP ; Окончание процедуры

 

MMM PROC FAR

 

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

; Выполнение соглашений DOS

PUSH DS ;Запись содержимого DS в стек

SUB AX, AX ;Запись ноля

PUSH AX ;в стек

; Установка верного значения в регистре DS.

; Регистры CS и SS устанавливаются системой.

MOV AX, D ;Занести адрес

MOV DS, AX ; D в DS

 

 

LEA BX, A ; Для передачи параметра-ссылки

; используется регистр-модификатор bx

MOV CX, 5 ; В CX заносится счетчик цикла

CALL MAX ; Вызов подпрограммы

MOV SUM, AL ; В переменную SUM заносится

; max элемент 1-го массива

LEA BX, B ; Для передачи параметра-ссылки

; используется регистр-модификатор bx

MOV CX, 3 ; В CX заносится счетчик цикла

CALL MAX ; Вызов подпрограммы

ADD SUM, AL ; К переменной SUM добавляется

; max элемент 1-го массива,

;т.е. получается итоговый результат

 

RET ;Возврат в DOS

MMM ENDP

CODE ENDS ;Конец сегмента

END MMM ;Конец программы.

 

Сохранение регистров

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