Передача аргументов через стек
Этот способ наиболее часто используется для передачи аргументов при вызове процедур. Суть его заключается в том, что вызывающая процедура самостоятельно заносит в стек передаваемые данные, после чего производит вызов вызываемой процедуры. Ранее мы рассматривали процессы, происходящие при передаче управления процедуре и возврате из нее. При этом мы обсуждали содержимое стека до и после передачи управления процедуре (см. рис. 10.4 и 10.5). Как следует из этих рисунков, при передаче управления процедуре микропроцессор автоматически записывает в вершину стека два (для процедур типа near) или четыре (для процедур типа far) байта. Вы помните, что эти байты являются адресом возврата в вызывающую программу. Если перед передачей управления процедуре командой call в стек были записаны переданные процедуре данные или указатели на них, то они окажутся под адресом возврата.
При рассмотрении архитектуры микропроцессора мы выяснили, что стек обслуживается тремя регистрами: ss, sp и bp. Микропроцессор автоматически работает с регистрами ss и sp в предположении, что они всегда указывают на вершину стека. По этой причине их содержимое изменять не рекомендуется. Для осуществления произвольного доступа к данным в стеке архитектура микропроцессора имеет специальный регистр ebp\bp (Base Point — указатель базы). Так же как и для регистра esp\sp, использование ebp\bp автоматически предполагает работу с сегментом стека. Перед использованием этого регистра для доступа к данным стека его содержимое необходимо правильно инициализировать, что предполагает формирование в нем адреса, который бы указывал непосредственно на переданные данные. Для этого в начало процедуры рекомендуется включить дополнительный фрагмент кода. Он имеет свое название — пролог процедуры. Типичный фрагмент программы, содержащий вызов процедуры с передачей аргументов через стек, может выглядеть так:
Код пролога состоит всего из двух команд. Первая команда push bp сохраняет содержимое bр в стеке с тем, чтобы исключить порчу находящегося в нем значения в вызываемой процедуре. Вторая команда пролога mov bp,sp настраивает t bp на вершину стека. После этого мы можем не волноваться о том, что содержимое sp перестанет быть актуальным, и осуществлять прямой доступ к содержимому стека. Что мы и делаем. Для доступа к arg_n достаточно сместиться от содержимого bр на 4, для arg_{n+1} — на 6 и т. д. Но эти смещения подходят только для процедур типа near. Для far-процедур эти значения необходимо скорректировать еще на 2, так как при вызове процедуры дальнего типа в стек записывается полный адрес — содержимое cs и ip. Поэтому для доступа к arg_n в строке 9 команда будет выглядеть mov ax,[bp+6], а для arg_{n+1}, соответственно, mov ax,[bp+6] и т. д.
Конец процедуры также должен быть оформлен особым образом и содержать действия, обеспечивающие корректный возврат из процедуры. Фрагмент кода, выполняющего такие действия, имеет свое название — эпилог процедуры. Код эпилога должен восстановить контекст программы в точке вызова вызываемой процедуры из вызывающей программы. При этом, в частности, нужно откорректировать содержимое стека, убрав из него ставшие ненужными аргументы, передававшиеся в процедуру. Это можно сделать несколькими способами:
· используя последовательность из n команд pop хх. Лучше всего это делать в вызывающей программе сразу после возврата управления из процедуры;
· откорректировать регистр указателя стека sp на величину 2*n, например, командой add sp,NN, где NN=2*n, и n — количество аргументов. Это также лучше делать после возврата управления вызывающей процедуре;
· используя машинную команду ret n в качестве последней исполняемой команды в процедуре, где n — количество байт, на которое нужно увеличить содержимое регистра esp\sp после того, как со стека будут сняты составляющие адреса возврата. Видно, что этот способ аналогичен предыдущему, но выполняется автоматически микропроцессором.
В каком виде можно передавать аргументы в процедуру? Мы уже упоминали, что передаваться могут либо данные, либо их адреса (указатели на данные). В языке высокого уровня это называется передачей по значению и адресу соответственно.
Наиболее простой способ передачи аргументов в процедуру — передача по значению. Этот способ предполагает, что передаются сами данные, то есть их значения. Вызываемая программа получает значение аргумента через регистр или через стек. Естественно, что при передаче переменных через регистр или стек на их размерность накладываются ограничения, связанные с размерностью используемых регистров или стека. Другой важный момент заключается в том, что при передаче аргументов по значению в вызываемой процедуре обрабатываются их копии. Поэтому значения переменных в вызывающей процедуре не изменяются.
Способ передачи аргументов по адресу предполагает, что вызываемая процедура получает не сами данные, а их адреса. В процедуре нужно извлечь эти адреса тем же методом, как это делалось для данных, и загрузить их в соответствующие регистры. После этого, используя адреса в регистрах, следует выполнить необходимые операции над самими данными. В отличие от способа передачи данных по значению, при передаче данных по адресу в вызываемой процедуре обрабатывается не копия, а оригинал передаваемых данных. Поэтому при изменении данных в вызываемой процедуре они автоматически изменяются и в вызывающей программе, так как изменения касаются одной области памяти.