5.2.1. Передача параметров

Процедуры могут получать или не получать параметры из вызывающей про­цедуры и могут возвращать или не возвращать результаты (процедуры, которые что-либо возвращают, называются функциями в языке Pascal, но ассемблер не

делает каких-либо различий между ними).

Параметры можно с помощью одного из шести механизмов:

по значению; Q по ссылке;

а по возвращаемому значению;

по результату; а по имени;

а отложенным вычислением.

Параметры можно передавать в одном из пяти мест:

в регистрах; □ в глобальных переменных; а в стеке; Q в потоке кода;

в блоке параметров.

Следовательно, всего в ассемблере возможно 30 различных способов передачи параметров для процедур. Рассмотрим их по порядку.

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

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

mov       ах,word ptr value ;  Сделать копию значения,

call      procedure ;   Вызвать процедуру.

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

Процедуре передается не значение переменной, а ее адрес, по которому проце­дура сама прочитает значение параметра. Этот механизм удобен для передачПроцедуры и функции |;

больших массивов данных и в тех случаях, когда процедура должна модифициро­вать параметры (хотя он и медленнее из-за того, что процедура будет выполнять дополнительные действия для получения значений параметров).

mov       ах,offset value call procedure

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

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

mov     . global.variable,offset value call procedure

[...]

procedure proc near

mov dx,global_variable

mov ax,  word ptr [dx]

(команды,  работающие с AX в цикле десятки тысяч раз)

mov word ptr [dx],ax

procedure endp

Передача параметров по результату

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

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

Данный механизм используют макроопределения, директива EQTJ, а также, например, препроцессор С во время обработки команды #define. При реализации этого механизма в компилирующем языке программирования (к которому отно­сится и ассемблер) приходится заменять передачу параметра по имени другими

механизмами с помощью, в частности, макроопределений.

Если установлено макроопределение

pass_by_name       macro parameterl mov       ax, parameterl

endm

то теперь параметр в программе можно передавать следующим образом: pass_by_name value

call procedure

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

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

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

Рассказав об основных механизмах передачи параметров процедуре, рассмот­рим теперь варианты, где их передавать.

Передача параметров в регистрах

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

Передача параметров в глобальных переменных

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

Этот метод считается неэффективным, и его использование может привести к тому,

что рекурсия и повторная входимость станут невозможными. Передача параметров в стеке

Параметры помещаются в стек сразу перед вызовом процедуры. Именно этот метод используют языки высокого уровня, такие как С и Pascal. Для чтения пара­метров из стека обычно применяют не команду POP, а регистр ВР, в который мещают адрес вершины стека после входа в процедуру:

push

parameter!

;  Поместить параметр в стек.

push

parameter2

 

call

procedure

 

add

sp,4

;   Освободить стек от параметров.

[... ]

e

proc near

 

push

bp

 

mov

bp, sp

 

(команды, которые могут

использовать стек)

mov

.

;  Считать параметр 2.

Его адрес в сегменте стека BP + 4,   потому что при выполнении команды CALL в стек поместили адрес возврата - 2 байта для процедуры '.

типа NEAR (или 4 - для FAR), а потом еще и BP - 2 байта.

Процедуры и функции »|

mov       bx,[bp+6] ;   Считать параметр 1.

(остальные команды) pop bp ret

procedure endp

Параметры в стеке, адрес возврата и старое значение ВР вместе называются активизационной записью функции.

Для удобства ссылок на параметры, переданные в стеке, внутри функции иног­да используют директивы чтобы не писать каждый раз точное смещение параметра от начала активизационной записи (то есть от ВР), например так:

 

push

X

 

push

Y

 

push

Z

 

call

xyzzy

 

[...3

 

xyzzy

proc

near

xyzzy_z

equ

[bp+8]

xyzzy_y

equ

tbp+6]

xyzzy_x

equ

[bp+4]

 

push

bp

 

mov

bp, sp

 

(команды,   которые i

 

mov

ax,xyzzy_x

 

(остальные команды)

 

pop

bp

 

ret

6

xyzzy

endp

 

; Считать параметр X.

При внимательном анализе этого метода передачи параметров возникает сразу два вопроса: кто должен удалять параметры из стека, процедура или вызывающая ее программа, и в каком порядке помещать параметры в стек. В обоих случаях ока­зывается, что оба варианта имеют свои «за» и «против». Так, например, если стек освобождает процедура (командой RET число_байтов), то код программы полу­чается меньшим, а если за освобождение стека от параметров отвечает вызываю­щая функция, как в нашем примере, то становится возможным последовательными командами CALL вызвать несколько функций с одними и теми же параметрами. Первый способ, более строгий, используется при реализации процедур в языке

Pascal, а второй, дающий больше возможностей для оптимизации, - в языке С. Ра­зумеется, если передача параметров через стек применяется и для возврата ре­зультатов работы процедуры, из стека не надо удалять все параметры, но попу­лярные языки высокого уровня не пользуются этим методом. Кроме того, в языке С параметры помещают в стек в обратном порядке (справа налево), так что стано­вятся возможными функции с изменяемым числом параметров (как, например, printf- первый параметр, считываемый из [ВР+4], определяет число остальных параметров). Но подробнее о тонкостях передачи параметров в стеке рассказано далее, а здесь приведен обзор методов.

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

В этом необычном методе передаваемые процедуре данные размещаются пря­мо в коде программы, сразу после команды CALL (как реализована процедура

print в одной из стандартных библиотек процедур для ассемблера UCRLIB): call print

db    1     "This ASCIZ-line will be printed",О

(следующая команда)

Чтобы прочитать параметр, процедура должна использовать его адрес, кото­рый автоматически передается в стеке как адрес возврата из процедуры. Разуме­ется, функция должна будет изменить адрес возврата на первый байт после конца переданных параметров перед выполнением команды RET. Например, процедуру print можно реализовать следующим образом:

print

 

proc near

 

push

bp

 

mov

bp, sp

 

push '

ax

 

push

si

 

mov

si,[bp+2]

Прочитать адрес ; возврата/начала данных.

eld

 

; Установить флаг направления ; для команды ЪоТзЬ.

readchar:

 

 

lodsb

 

; Прочитать байт из строки.

test

al.al

Eсли это 0   (конец строки),

jz

print_done

вывод строки закончен.

int

29h

Вывести символ в AL на экран.

jmp

short print_readchar

done:

 

 

mov

[bp+2],si

;  Поместить новый адрес возврата

pop

si

 

pop

ax

 

pop

bp .

 

ret

endp

 

print

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

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

Блок параметров - это участок памяти, содержащий параметры, так же как и в предыдущем примере, но располагающийся обычно в сегменте данных.

в стек.

Процедуры и функции   , Щ

Процедура получает адрес начала этого блока при помощи любого метода переда­чи параметров (в регистре, в переменной, в стеке, в коде или даже в другом блоке параметров). В качестве примеров реализации этого метода можно назвать мно­гие функции DOS и BIOS - поиск файла, использующий блок параметров DTA, или загрузка (и исполнение) программы, использующая блок параметров ЕРВ.