Классификация подпрограмм
Использование подпрограмм (subroutine) или процедур (procedure) является одним из универсальных приемов программирования. Возможность работы с ними предусмотрена во всех языках программирования. Изначально идея заключалась в следующем: неоднократно выполняемые действия оформляются в виде самостоятельного фрагмента программы так, чтобы к нему можно было обратиться из любой ее точки и затем вернуться назад. Со временем эта идея развилась, появилась категория процедур, текст которых не описывается в программе, а готовится заранее, хранится в специальных библиотеках и доступен для любых программ. В комплект компиляторов с алгоритмических языков обычно включены библиотеки, содержащие процедуры различного назначения, в том числе и для работы с новым периферийным оборудованием.
В настоящем приложении описана техника составления процедур при программировании на Макроассемблере. Особое внимание уделено рассмотрению условий, при которых такие процедуры могут использоваться в программах, составленных на языках высокого уровня Си, Паскаль, Фортран и др. Именно ради этого данное приложение включено в текст книги.
При работе с Макроассемблером подпрограммы (процедуры) делятся на ближние и дальние, внутренние и внешние. Два первых термина характеризуют способ вызова подпрограммы и возврата из нее, а два вторых – локализацию подпрограмм по отношению к тексту задачи.
Ближние подпрограммы
При входе в ближнюю (near) подпрограмму и при возврате из нее текущее содержимое сегментного регистра cs не изменяется. Это означает, что вызов ближней подпрограммы возможен только из того сегмента, в котором она описана. Все подпрограммы, приведенные в примерах основной части книги и ее приложений, являются ближними, поэтому они могут располагаться только в разделе кодов задачи.
От простой группы команд ближняя подпрограмма отличается только тем, что первая команда обязательно имеет метку (имя), а последней выполняемой командой является ret или retn (это два разных имени одной инструкции). Она (ret) просто выталкивает содержимое верхушки стека в счетчик команд (регистр IP) и увеличивает адрес указателя стека на 2, т. е. равноценна команде pop ip. В результате происходит возврат на вызывающий модуль.
Выражение "последняя выполняемая команда" надо понимать буквально, ret завершает не текст подпрограммы, а ее выполнение. В простых случаях она может завершать текст подпрограммы, но если последняя имеет разветвления, то каждая ветвь может заканчиваться командой ret.
Обращение к подпрограмме (ее вызов) выполняет специальная команда сан, содержащая адрес точки входа. Для его указания можно использовать все стандартные способы адресации, например, имя точки входа, явное задание адреса, выбор адреса из регистра и т. д. Команда call помещает в стек адрес возврата и выполняет безусловный переход на указанную точку входа. Адресом возврата является текущее содержимое счетчика команд (IP). После выборки кода инструкции и операндов IP всегда содержит адрес начала следующей команды. Таким образом, специальная команда call нужна для того, чтобы сформировать в стеке адрес возврата для команды ret, завершающей выполнение подпрограммы.
Подпрограмма может работать, и обычно работает, со стеком. Причем к моменту выполнения команды ret в верхушке стека должен находиться адрес возврата. Сказанное не означает, что его нельзя изменять. Это делается в особых случаях, когда по каким-то причинам надо вернуться не на вызывающий модуль, а в любое другое место задачи.
Дальние подпрограммы
Дальняя (far) подпрограмма отличается от ближней тем, что она расположена не в том сегменте, в котором находится вызывающий модуль. Поэтому при обращении к удаленной подпрограмме изменяется содержимое не только счетчика команд (IP), но и кодового сегментного регистра (cs).
Мнемоническим именем команды вызова в любом случае является call, но ему могут соответствовать разные коды операций (машинных инструкций). Обнаружив в тексте команду call, Макроассемблер анализирует описание указанного в ней имени, и в зависимости от его типа (far или near) выбирает нужный код операции вызова подпрограммы. В частности, если имя соответствует удаленной процедуре, то будет выбран код операции, при выполнении которого в стек записывается сначала содержимое сегментного регистра cs, а затем счетчика команд IP. Таким образом, при входе в дальнюю подпрограмму в верхушке стека находится исходное значение IP, а перед ним – значение cs.
Последней выполняемой командой дальней подпрограммы является retf, она выталкивает из верхушки стека не одно, а два слова. Первое слово выталкивается в счетчик команд IP, а второе – в сегментный регистр cs. В результате в регистрах cs:ip оказывается полный адрес точки возврата.
Сегмент, содержащий дальнюю подпрограмму, может входить в тело задачи или располагаться во внешнем модуле. Во втором случае подпрограмма оказывается доступной не для одной, а для разных задач.