Подпрограммы (процедуры)
Процедуры могут получать или не получать параметры из вызывающей процедуры и могут возвращать или не возвращать результаты. Процедуры, которые что-либо возвращают, называют функциями (Паскаль).
Различают два основных механизма передачи параметров процедуре:
- по значению;
- по ссылке.
Пусть procedure - имя процедуры, а value - имя некоторой области памяти (переменная). Тогда вызов процедуры с передачей параметра по значению выглядит следующим образом:
mov AX, word ptr value ; процедура получает копию
call procedure ; входного параметра
Вызов с передачей параметра по ссылке будет выглядеть так:
mov AX, offset value ; процедура получает адрес
call procedure ; входного или выходного параметра
Для обмена информацией между вызывающим кодом и процедурой могут использоваться такие каналы:
- регистры;
- стек;
- глобальные переменные;
- поток кода;
- блок параметров.
Передача параметров в регистрах
Если число параметров и сами параметры невелики, лучшим метсом для их передачи являются регистры. Примерами могут служить вызовы функций DOS и BIOS. Языки высокого уровня обычно используют регистр AX (EAX) для размещения результата, который возвращается функцией.
Передача параметров через стек
Параметры помещаются в стек перед вызовом процедуры. Именно такой метод используют языки высокого уровня (C, Pascal). Для чтения параметров из стека применяют не команду POP, а регистр BP, в который помещают адрес вершины стека после входа в процедуру:
push параметр1 ; поместить параметр в стек
push параметр2
call procedure
add SP, 4 ; освободить стек от параметров
............
procedure PROC near
push BP
mov BP, SP
(команды, которые могут использовать стек)
mov AX,
[BP+4] ; считать параметр2. Его адрес [BP+4], потому что ; при выполнении
команды call в стек поместили адрес возврата (2
байта для
; процедуры типа NEAR), а потом еще и
BP - 2 байта.
mov BX, [BP+6] ; считать параметр1.
(остальные команды)
pop BP
ret
procedure ENDP
При использовании стека для передачи параметров процедуры возникает два вопроса: кто должен удалять параметры из стека (процедура или вызывающий код) и в каком порядке помещать параметры в стек. Все возможные варианты ответа на этот вопрос имеют свои "за" и "против". Так, например, если стек освобождает процедура (команда ret число_байтов), то код программы получается меньшим, а если за освобождение стека от параметров отвечает вызывающая функция, то становится возможным последовательными командами CALL вызвать несколько функций с одними и теми же параметрами. Первый способ - более строгий (Pascal), второй - дает больше возможностей для оптимизации (С). Кроме того, в языке С параметры помещают в стек в обратном порядке (справа налево), так что становятся возможными функции с изменяемым числом параметров.
Передача параметров в потоке кода
Передаваемые данные размещаются прямо в коде программы, сразу после команды call, например (так реализовна процедура print в одной из стандартных библиотек процедур для ассемблера UCRLIB):
call print
DB "This ASCIZ-line will be printed", 0
(следующая команда)
Чтобы прочитать параметр, процедура должна использовать его адрес, который автоматически передается в стеке как адрес возврата из процедуры. При этом адрес возврата должен быть изменен на первый байт после конца блока данных перед выполнением команды RET.
Лабораторное задание 3_2.
Передача параметров в потоке кода (так же как и передача параметров в стеке в обратном порядке) позволяет передавать различное число параметров или один параметр различной длины.
Передача параметров в блоке параметров
Блок параметров - участок памяти, содержащий параметры и располагающийся обычно в сегменте данных. Процедура получает адрес начала такого блока. Таким методом реализованы многие функции DOS и BIOS (поиск файла - использует блок параметров DTA, загрузка и исполнение программы - используется блок параметров EPB).
Локальные переменные
Наиболее распространенный способ хранения локальных переменных - стек. Принято распологать локальные переменные сразу после сохраненного значения регистра BP, так что на них можно ссылаться как [BP-2], [BP-4], [BP-6] и т.д.
Рассмотрим процедуру examp, в которой используются параметры x, y, z и локальные переменные a, b, c (все эти величины предполагаются 2-х байтовыми):
examp proc near
x equ [BP+8] ; параметры процедуры
y equ [BP+6]
z equ [BP+4]
a equ [BP- 2] ; локальные переменные
b equ [BP- 4]
c equ [BP- 6]
push BP ; сохранить BP
mov BP, SP ; установить BP для себя
sub SP, 6 ; зарезервировать 6 байт для локальных переменных
(тело процедуры)
mov SP, BP ; выбросить из стека локальные переменные
pop BP ; восстановить BP вызвавшей процедуры
ret 6 ; возврат с удалением трех параметров из стека
examp endp
При вызове процедуры examp стек будет выглядеть следующим образом:
[BP+8] = x
[BP+6] = y
[BP+4] = z
[BP+2] = IP
BP® [BP+0] = BP
[BP- 2] = a
[BP- 4] = b
SP® [BP- 6] = c
¯
В связи с тем, что последовательности команд
push BP
mov BP, SP
sub SP, 6
и
mov SP, BP
pop BP
используются очень часто при построении процедур, были введены специальные команды ENTER и LEAVE, которые их заменяют. С использованием этих команд процедура examp будет выглядеть следующим образом:
examp proc near
x equ [BP+8]
y equ [BP+6]
z equ [BP+4]
a equ [BP- 2]
b equ [BP- 4]
c equ [BP- 6]
enter 6, 0 ; второй параметр - уровень вложенности процедуры
(тело процедуры)
leave
ret 6
examp endp
Активизационной записью процедуры называется следующая информация, размещаемая в стеке: параметры процедуры, IP (адрес возврата), BP (предыдущее значение).
Стековый кадр - область стека, отведенная для активизационной записи и локальных переменных процедуры.