Использование подпрограмм
Преобразуем пример 3.8а так, чтобы единственный в этом примере параметр (условная величина задержки) передавался в подпрограмму не через регистр SI, а через стек.
Вызов подпрограммы delay в этом случае должен выполняться следующим образом:
push 2000; Проталкиваем в стек значение параметра call delay; Вызываем подпрограмму delay
Текст подпрограммы подвергнется значительным изменениям:
Пример 3.8 в. Передача параметра через стек.
delay proc; Процедура-подпрограмма push CX; Сохраним СХ основной программы push BP; Сохраним BP mov BP,SP; Настроим BP на текущую вершину стека mov CX, [BP+6]; Скопируем из стека параметр del1: push CX; Сохраним его mov CX,0; Счетчик внутреннего цикла del2 loop del2; Внутренний цикл(64К шагов) pop CX; Восстановим внешний счетчик loop del1; Внешний цикл pop BP; Восстановим BP pop CX; и СХ программы ret 2; Возврат и снятие со стека ненужного уже параметра
Команда call, передавая управление подпрограмме, сохраняет в стеке адрес возврата в основную программу. Подпрограмма сохраняет в стеке еще два 16-разрядных регистра. В результате стек оказывается в состоянии, изображенном на рис. 3.9.
После сохранения в стеке исходного содержимого регистра ВР (в основной программе нашего примера этот регистр не используется, однако в общем случае это может быть и не так), в регистр ВР копируется содержимое указателя стека, после чего в ВР оказывается смещение вершины стека. Далее командой mov в регистр СХ заносится содержимое ячейки стека, на 6 байтов ниже текущей вершины. В этом месте стека как раз находится передаваемый в подпрограмму параметр, как это показано в левом столбце рис. 3.8. Конкретную величину смещения относительно вершины стека надо для каждой подпрограммы определять индивидуально, исходя из того, сколько слов сохранено ею в стеке к этому моменту. Напомним, что при использовании косвенной адресации с регистром ВР в качестве базового, по умолчанию адресуется стек, что в данном случае и требуется.
Рис. 3.8. Состояние стека в подпрограмме после сохранения регистров.
Параметр, полученный таким образом, используется далее в подпрограмме точно так же, как и в примере 3.8 а.
Выполнив возложенную на нее задачу, подпрограмма восстанавливает сохраненные ранее регистры и осуществляет возврат в основную программу с помощью команды ret, в качестве аргумента которой указывается число байтов, занимаемых в стеке отправленными туда перед вызовом подпрограммы параметрами. В нашем случае единственный параметр занимает 2 байт. Если здесь использовать обычную команду ret без аргумента, то после возврата в основную программу параметр останется в стеке, и его надо будет оттуда извлекать (между прочим, не очень понятно, куда именно, ведь все регистры у нас могут быть заняты). Команда же с аргументом, осуществив возврат в вызывающую программу, увеличивает содержимое указателя стека на значение ее аргумента, тем самым осуществляя логическое снятие параметра. Физически этот параметр, как, впрочем, и все остальные данные, помещенные в стек, остается в стеке и будет затерт при дальнейших обращениях к стеку.
Разумеется, в стек можно было поместить не один, а сколько угодно параметров. Тогда для их чтения надо было использовать несколько команд mov со значениями смещения ВР+6, ВР+8, BP+0Ah и т.д.
Рассмотренная методика может быть использована и при дальних вызовах подпрограмм, но в этом случае необходимо учитывать, что дальняя команда call сохраняет в стеке не одно, а два слова, что повлияет на величину рассчитываемого смещения относительно вершины стека.