Параметры в стеке
Характерной особенностью подпрограмм является то, что используемые при вычислениях величины передаются им в виде входных параметров. В свою очередь, подпрограммы могут возвращать результаты вычислений в виде выходных параметров. Способ доступа к параметрам зависит от того, где они расположены.
Наиболее простой формой является передача параметров в регистрах общего назначения. Она используется процедурами BIOS и DOS. В этом случае доступ к параметрам осуществляется наиболее просто и быстро, но существует ряд ограничений, сводящих на нет это преимущество. Здесь для нас существенно одно из таких ограничений, а именно то, что компиляторы с алгоритмических языков обычно не используют регистры для передачи параметров. В некоторых из них, например в Си++, предусмотрена возможность размещения параметров в регистрах, но это уже относится к категории трюков или уловок, но не к стандартам программирования.
Если создаваемая вами подпрограмма предназначена для использования в программах, составленных на одном из алгоритмических языков, то она должна быть рассчитана на расположение параметров в стеке, поскольку в большинстве случаев компиляторы размещают их именно в нем. К описанию особенностей компиляторов мы вернемся после обсуждения техники передачи параметров при программировании на ассемблере.
Общие сведения
Стеком называется любой произвольно выбранный блок оперативной памяти, работа с которым производится по принципу "последнее записанное – первое считанное" (LIFO – last in first out). Иначе говоря, выражение "стек" характеризует не тип памяти, а способ работы с ней.
В процессе выполнения задачи в регистре ss хранится код сегмента оперативной памяти, в котором расположена область стека, а текущий адрес (смещение) верхушки стека в этом сегменте хранится в регистре SP, который называется указателем стека. Разрядность регистров зависит от режима работы микропроцессора: в реальном режиме они содержат по 16 разрядов, а в защищенном режиме по 32 разряда.
Стек нарастает в сторону уменьшения адресов, поэтому при входе в задачу регистр зр содержит наибольший доступный адрес в области стека. При записи данных в стек адрес, хранящийся в зр, уменьшается, а при их выборке из стека – увеличивается.
Для записи и чтения данных в режиме LIFO предназначены команды PUSH и POP, которые неоднократно использовались в примерах. Команда push предварительно уменьшает содержимое регистра sp, а затем записывает операнд в вычисленный адрес. Команда pop, наоборот, сначала считывает операнд, а затем увеличивает содержимое зр. В обоих случаях адрес, хранящийся в sp, изменяется на размер операнда, который может составлять 2 или 4 байта. Один байт записать в стек нельзя, команда push преобразует его в слово.
Кроме команд push и pop в режиме LIFO со стеком работают команды вызова обычных (сан) и прерывающих (int) подпрограмм и команды возврата из них (ret и iret).
Для непосредственного доступа к области стека выделен специальный регистр ВР. Он может использоваться в обычных командах (пересылки, сложения и пр.) в тех случаях, когда один их операндов расположен в области стека. Если операнд находится в регистре bp, то при его обработке микропроцессор, по умолчанию, выбирает в качестве сегментного регистра ss, a не DS, как обычно. В случае необходимости можно явно указать любой другой сегментный регистр.
Новое макроопределение
Перед обращением к подпрограмме в стек записываются ее параметры. Запись в стек обычно выполняет команда push. Для сокращения текста программы и придания ему большей наглядности можно использовать специальный макровызов. Текст соответствующего макроопределения приведен в примере В.4.
Пример В.4. Макроопределение для вызова подпрограмм.
@Invoke macro name, par; заголовок макроопределения irp r, <par>; начало оператора повторения push r; заготовка повторяемой команды endm; конец оператора повторения call name; заготовка команды вызова подпрограммы endm; конец текста макроопределения
В приведенных ранее примерах неоднократно использовался макровызов FushReg, текст его макроопределения приведен в примере 2.12. В данном случае к этому тексту добавилась только одна строка, содержащая заготовку команды сан. Поэтому в результате макроподстановки в текст программы сначала будет включена группа команд push, а затем команда call.
Если макроопределение примера В.4 включено в текст основной задачи, то для его использования в нужном месте указывается следующий макровызов:
@Invoke имя_процедуры <список_параметров>
Имя процедуры может быть как внешним, так и внутренним. Список параметров обязательно заключается в угловые скобки, а параметры отделяются друг от друга запятыми, после которых допустим пробел. Форма записи параметров стандартная для команды push. Пример использования макровызова приведен в конце данного раздела.