- •1.2.2. Команды управления циклом
- •1.2.3. Работа с массивами
- •1.3. Задание на лабораторную работу
- •1.4. Отчет по лабораторной работе
- •2. Лабораторная работа № 4
- •2.1. Общие методические указания по выполнению лабораторной работы
- •2.2. Теоретические сведения
- •2.2.1. Стек и сегмент стека
- •2.2.2. Стековые команды
- •2.2.3 Приемы работы со стеком
- •2.3. Задание на лабораторную работу
- •2.4. Отчет по лабораторной работе
- •3 .Лабораторная работа № 5
- •3.1. Общие методические указания по выполнению лабораторной работы
- •3.2. Теоретические сведения
- •3.2.1. Дальние переходы
- •3.2.2. Подпрограммы (процедуры)
- •3.3. Задание на лабораторную работу
- •3.4. Отчет по лабораторной работе
- •4. Лабораторная работа №6.
- •4.1. Общие методические указания по выполнению лабораторной работы
- •4.2. Теоретические сведения
- •4.3.Задание на лабораторную работу
- •4.4. Отчет по лабораторной работе
3.2.2. Подпрограммы (процедуры)
Довольно часто в программах, особенно больших, приходится несколько раз решать одну и ту же подзадачу и потому приходится многократно выписывать одинаковую группу команд, решающих эту подзадачу. Чтобы избежать повторного выписывания такой группы команд, ее обычно выписывают один раз и оформляют соответствующим образом, а затем в нужных местах программы просто передают управление на эти команды, которые, проработав, возвращают управление обратно. Такая группа команд, которая решает некоторую подзадачу и которая организована таким образом, что ее можно использовать любое число раз и из любых мест, называется подпрограммой. По отношению к подпрограмме остальную часть программы принято называть основной программой. Аналог подпрограмм на языках высокого уровня называется процедурой.
Размещение подпрограммы
Обычно подпрограммы (п/п) размещают либо в конце сегмента команд за командой FINISH (см. рисунок а), либо в самом начале этого сегмента - перед той командой, с которой должно начинаться выполнение программы (см. рисунок б). В больших программах подпрограммы нередко размещают в отдельном сегменте команд (см. рисунок в).
Варианты размещения подпрограммы в тексте программы
Оформление подпрограммы
Описание подпрограммы в виде процедуры выглядит так:
<имя процедуры> PROC <параметр>
<тело процедуры>
<имя процедуры> ENDP
Перед телом процедуры (ее командами) ставится директива PROC (procedure), а за ним - директива ENDP (end of procedure). В обеих этих директивах указывается одно и то же имя - имя, присвоенное процедуре.
У директивы PROC есть параметр - это либо NEAR (близкий), либо FAR (дальний). Параметр может и отсутствовать, тогда считается, что он равен NEAR (в связи с этим параметр NEAR обычно не указывается). При параметре NEAR или при отсутствии параметра процедура называется "близкой", при параметре FAR - "дальней". К близкой процедуре можно обращаться только из того сегмента команд, где она описана, и нельзя обращаться из других сегментов, а к дальней процедуре можно обращаться из любых сегментов команд (в том числе и из того, где она описана). В этом и только в этом различие между близкими и дальними процедурами.
Вызов процедур и возврат из них
Существуют специальные команды, упрощающие реализацию вызова процедуры и возврат из нее.
Команда вызова процедуры записывается следующим образом:
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 ;Конец программы.
Сохранение регистров
Процедура, используя регистры для своих целей, портит значения, которые, возможно, нужны вызывающей программе. Поэтому в начале процедуры необходимо сохранить в стеке значения всех используемых процедурой регистров, а в конце – восстановить их. Кроме, естественно, тех регистров, через которые возвращается результат работы процедуры.
Передача параметров через стек
Существует еще один способ передачи параметров: вызывающая программа должна перед вызовом процедуры записать параметры в стек.
Различают передачу параметра по значению (в стек записывается значение параметра) и по ссылке (в стек записывается адрес переменной-параметра). Чтобы потом можно было обратиться к параметрам процедуры, следует воспользоваться регистром BP (сначала передать в BP адрес вершины стека, а затем использовать выражения вида [BP+i] для доступа к параметрам процедуры). Но сначала надо сохранить значение регистра BP в стеке, чтобы после окончания работы процедуры восстановить его обратно.
В конце работы процедура должна удалить параметры из стека. Для этого в команду RET добавляется числовой параметр, указывающий, сколько байтов надо удалить из стека (если параметр не указывается, то команда воспринимается как RET 0, т.е. возврат из процедуры без очистки стека).
RET 2*k ;удаление k параметров
Пример. Создать процедуру обнуления n байтов памяти по адресу X. Параметры передаются через стек.
S SEGMENT STACK ; Сегмент стека
DB 200 DUP(0ABh)
S ENDS
D SEGMENT ; Сегмент данных
X DB 20 DUP (1)
Y DB 10 DUP (2)
D ENDS
CODE SEGMENT ; Сегмент кода
ASSUME SS:S,CS:Code, DS:D
; Процедура обнуления n байтов памяти по заданному
; адресу. Параметры передаются через стек.
NUL PROC
PUSH BP; ; Сохранение значения
; регистра BP
MOV BP, SP ; Установка в BP
;адреса вершины стека
PUSH BX ; Сохранение текущих значений
PUSH CX ; регистров BX и CX
; Извлечение из стека параметров процедуры
; в обратном порядке
MOV CX, [BP+4] ; CX:=N (второй параметр – число
; байтов – в счетчик цикла)
MOV BX, [BP+6] ; (первый параметр – адрес массива
; байтов – в регистр-модификатор
; для косвенной адресации)
L1: MOV BYTE PTR [BX], 0 ; обнуление байта
;по адресу из BX (т.е. обнуление текущего
;элемента массива)
INC BX
LOOP L1 ; Окончание тела цикла
POP CX ; Восстановление значений
POP BX ; регистров BX и CX,
POP BP ; а также регистра BP
RET 4 ; Возврат из процедуры с
; очищением стека от параметров
NUL ENDP ; Окончание процедуры
; Основная программа
MMM PROC FAR
; Выполнение на входе соглашений DOS
PUSH DS ; Запись содержимого DS в стек
SUB AX,AX ; Запись ноля
PUSH AX ; в стек
; Установка верного значения в регистре DS.
; Регистры CS и SS устанавливаются системой.
MOV AX,D ; Занесение адреса
MOV DS,AX ; D в DS
; Обнуление 20 байт по адресу X
LEA AX, X
PUSH AX ; Передача в стек 1-го параметра
; процедуры – адреса памяти,
; которую нужно очистить
MOV AX, 20
PUSH AX ; Передача в стек 2-го параметра
; процедуры – размера очищаемой
; области памяти в байтах
CALL NUL ; вызов
; Обнуление 10 байт по адресу Y
LEA AX, Y
PUSH AX ; Передача в стек 1-го параметра
; процедуры
MOV AX, 10
PUSH AX ; Передача в стек 2-го параметра
; процедуры
CALL NUL
RET ;Возврат в DOS
MMM ENDP
CODE ENDS ;Конец сегмента кода
END MMM ;Конец программы