Использование стека для передачи параметров
Стек часто используется при обращении к процедурам для передачи параметров в процедуры. При передаче управления процедуре процессор автоматически записывает в вершину стека два (для процедур ближнего вызова) или четыре (для процедур дальнего вызова) байта – адрес возврата в вызывающую программу. Если предварительно в стек были записаны переданные процедуре параметры или указатели на них, то они окажутся под адресом возврата.
Ранее уже упоминалось, что для работы со стеком в процессоре предусмотрены три регистра SS, SP и BP. Микропроцессор автоматически работает с регистрами SS и SP в предположении, что они всегда указывают на вершину стека. По этой причине их содержимое изменять не рекомендуется. Для произвольного доступа к данным в стеке используется регистр BP. Для корректной работы с использованием этого регистра содержимое стека должно быть правильно проинициализировано, что предполагает формирование адреса в нем, который бы непосредственно указывал на переданные данные. Для этого в начала процедуры необходимо включить дополнительный фрагмент кода – пролог процедуры. Конец процедуры также должен быть оформлен особым образом для обеспечения корректного возврата из процедуры. Фрагмент кода, выполняющий эти действия, называется эпилогом процедуры. При этом нужно откорректировать содержимое стека, убрав из него ставшие ненужными аргументы, переданные и использованные в процедуре. Например, можно использовать последовательность из n команд вида POP регистр. Лучше всего это сделать в вызывающей программе сразу после возврата управления из процедуры.
Типичный фрагмент программы, содержащий вызов процедуры с передачей аргументов через стек, может выглядеть следующим образом:
s_s segment stack "stack"
dw 12 dup(?)
s_s ends
d_s segment
aa dw 10
d_s ends
c_s segment
assume ss:s_s,ds:d_s,cs:c_s
begin:
mov ax,d_s
mov ds,ax
push aa ;запись в стек аргумента
call pr1 ;вызов процедуры
pop ax ;очищаем стек, забирая аргумент в регистр ax
.mov ah, 4ch
int 21h ;завершение работы программы
pr1 proc near
;начало пролога
Push bp
Mov bp,sp
;конец пролога
mov ax,[bp+4] ;доступ к аргументу по адресу aa для процедуры
;в регистре ax будет значение 10
add ax,158h
mov dx,ax
;начало эпилога
mov sp,bp ;восстановление значения регистра sp
pop bp ;восстановление значения старого bp
;до входа в процедуру
ret ;возврат в вызывающую подпрограмму
;конец эпилога
pr1 endp
c_s ens
end begin
Код пролога состоит из двух команд: первая команда сохраняет содержимое регистра BP в стеке, чтобы исключить затирание находящегося в нем значения в вызываемой процедуре; вторая команда настраивает регистр BP на вершину стека для осуществления прямого доступа к содержимому стека.
Для доступа к последнему аргументу достаточно сместиться от содержимого BP на 4 (22 первых байта занимает адрес возврата, 2 следующих - искомое значение), к предпоследнему аргументу – на 6 и так далее (для процедур ближнего вызова).
Код эпилога процедуры восстанавливает состояние программы до момента входа в процедуру.
Что касается способов передачи параметров в процедуру через стек, то можно передавать либо сами данные (передача параметров по значению), либо их адреса (передача параметров по ссылке). Например, в приведенной выше программе использовался способ передачи аргументов по значению.
При передаче параметров через стек по значению на их размер накладываются ограничения, связанные с размерностью стека. Кроме того, в этом случае в вызываемой процедуре обрабатываются копии параметров. Таким образом, в приведенном выше примере значение по адресу aa в сегменте данных не изменится, то есть останется равным 10, независимо от выполняемых над этим значением действий в процедуре.
При передаче аргументов по ссылке в вызываемой процедуре обрабатывается не копия, а оригинал передаваемых данных. Поэтому при изменении данных в вызываемой процедуре они автоматически изменяются и в вызывающей программе, поскольку изменения касаются одной области памяти.