Использование подпрограмм
В примере 3.8 подпрограмма не требовала параметров. Чаще, однако, подпрограмма должна принимать один или несколько параметров и возвращать результат. В этом случае необходимо организовать взаимодействие основной программы и подпрограммы. Никаких специальных средств языка для этого не существует; передачу параметров в подпрограмму и из нее программист организует по своему усмотрению. Для передачи параметров как в одну, так и в другую сторону можно использовать регистры общего назначения, ячейки памяти или стек. Например, нетрудно преобразовать подпрограмму delay из примера 3-8 так, чтобы ей можно было передавать величину требуемой задержки. Пусть эта величина (в числе шагов внешнего цикла) передается в регистре SI.
Пример 3.8 а. Подпрограмма задержки с одним параметром, передаваемом в регистре SI.
delay proc; Процедура – подпрограмма push CX; Сохраним СХ основной программы mov CX,SI; Счетчик внешнего цикла del1: push CX; Сохраним его mov CX,0; Счетчик внутреннего цикла del2: loop del2; Внутренний цикл (64К шагов) pop CX; Восстановим внешний счетчик loop del1; Внешний цикл (2000 шагов) pop CX; Восстановим СХ программы ret; Возврат в программу
Можно пойти еще дальше и составить подпрограмму таким образом, чтобы передаваемый в нее параметр характеризовал время задержки в секундах. Если не связываться с использованием системного таймера в качестве инструмента для определения интервала времени, а по-прежнему реализовывать задержку с помощью процессорного цикла, ее величина будет зависеть от скорости работы конкретного компьютера и должна быть подобрана экспериментально. Приведенный ниже вариант подпрограммы правильно работал на процессоре Pentium с тактовой частотой 200 МГц.
Пример 3.8 б. Подпрограмма задержки с преобразованием параметра, передаваемого в регистре SI.
delay proc; Процедура-подпрограмма push AX; Сохраним все push BX; используемые push CX; в программе push DX; регистры mov AX,SI; первый сомножитель в AX mov BX,600; второй экспериментально подобранный сомножитель mul BX; Произведение в DX:AX mov CX,AX; Нам оно нужно в CX del1: push CX; Сохраним его mov CX,0; Счетчик внутреннего цикла del2: loop del2; внутренний цикл (64К шагов) pop CX; Восстановим внешний счетчик loop del1; Внешний цикл (2000 шагов) pop DX; Восстановим pop CX; все сохраненные pop BX; в начале подпрограммы pop AX; регистры ret; Возврат в программу
Эксперименты показали, что для получения правильной задержки значение параметра, обозначающее число секунд, следует умножать на 600. Поскольку при умножении в системе команд МП 86 первый сомножитель должен находиться в регистре АХ, а второй не может быть непосредственным значением и тоже, следовательно, должен быть помещен в один из регистров, и, к тому же, произведение занимает два регистра DX:AX, приходится сохранять при входе в подпрограмму не один регистр, как в предыдущем примере, а 4. Передаваемый в SI параметр переносится в АХ, в ВХ загружается второй сомножитель, а из полученного с помощью команды mul произведения используется младшая часть, находящаяся в АХ. Таким образом, для данного варианта подпрограммы значение задержки не должно превышать 109 с (109 х 600 = 65500, что почти совпадает с максимально возможным значением 65535).
Следует обратить внимание на опасность, подстерегающую нас при выполнении операции умножения. Пусть значение передаваемого параметра составляет всего 5. При умножении на 600 получится число 3000, которое безусловно помещается в регистре АХ. Однако операция умножения 16-разрядных операндов: mul BX всегда, независимо от конкретной величины произведения, помещает его в пару регистров DX:AX, и, следовательно, при небольшой величине произведения регистр DX будет обнуляться. Поэтому, хотя мы и не используем старшую часть произведения и фактически ее может и не быть, сохранение и последующее восстановление регистра DX является обязательным.
Передача параметров в подпрограмму через регистры общего назначения или даже через сегментные регистры вполне возможна, однако на практике для передачи параметров чаще всего используют стек, хотя бы потому, что регистров немного, а в стек можно поместить любое число параметров. При этом применяется своеобразная методика работы со стеком не с помощью команд push и pop, а с помощью команд mov с косвенной адресацией через регистр ВР, который архитектурно предназначен именно для адресации к стеку.