Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Пильщиков

.pdf
Скачиваний:
156
Добавлен:
09.05.2015
Размер:
6.35 Mб
Скачать

200 Программирование на языке ассемблера IBM PC Глава 1

Число фактических параметров, указываемых в макрокоманде, вообще говоря, должно равняться числу формальных параметров макроса, причем i-й фактический параметр соответствует i-му формальному параметру. Однако, если фактических параметров указано больше, чем надо, то лишние фактические параметры игнорируются, а если меньше, то считается, что в качестве недостающих фактических параметров заданы пустые тексты.

1 U J . Макроподстановки и макрорасширения

Теперь рассмотрим, что происходит с макрокомандами. Когда макрогенератор, просматривая исходный текст программы, встречает макрокоманду, то он выполняет макроподстановку: находит описание макроса с указанным именем, берет его тело, заменяет в этом теле все формальные параметры на соответствующие фактические параметры и полученный таким образом текст (он называется макрорасширением) подставляет в программу вместо макрокоманды.

Примеры (над стрелкой указано, какие формальные параметры на какие фактические параметры заменяются):

В общем случае макрокоманда заменяется на несколько команд, т. е. она как бы представляет собой целую группу команд. Этим и объясняется название "макрокоманда" - "большая команда".

11-3.4. Примеры использования макросов

Пример 1 (описание крупных операций в виде макросов)

Одним из существенных недостатков машинного языка, который сохраняется и в ЯА, является то, что при программировании на этих языках приходится применять мелкие операции. Например, ЭВМ умеет складывать два числа, но вот три числа она сама по себе уже сложить не может, поэтому мы вынуждены указывать ей, как это делается. И такое сведение к мелким операциям приходится делать для любого алгоритма, сколь бы сложным он ни был. В этом

изаключается основная сложность программирования на ЯА.

Вопределенной мере этот недостаток можно устранить с помощью макросов. Для этого надо в виде макросов описать более крупные операции, а затем составлять программу с использованием этих макросов.

Пусть, к примеру, в нашей программе многократно встречается условный переход "по меньше": if х<у then goto L. Эта операция реализуется тремя командами. Чтобы их каждый раз не выписывать заново, имеет смысл описать их как макрос, а затем пользоваться им. Давайте так и сделаем. Дадим этому макросу имя if_less и будем считать, что числа у нас знаковые и размером в слово:

 

Макросредства

2 0 1

I F _ L E S S MACRO X , Y , L

 

MOV

AX,X

 

CMP A X , Y

 

J L

L

 

ENDM

 

Имея такой макрос, можно, к примеру, следующим образом описать вычисление минимума трех чисел DX=min(A,B,C):

Слева указан текст программы, который составляет ее автор, а справа - те команды, которые будет реально выполняться. (Замечание: если макрокоманда помечена меткой, то в макрорасширении эта метка размещается в отдельной строке, а тело макроса начинается со следующей строки, т. к. в общем случае первая команда тела макроса может быть помечена своей меткой.)

Как видно, использование макросов сокращает размеры исходного текста программы и, что не менее важно, позволяет составлять программу в терминах более крупных операций. Если в виде макросов описать все часто используемые операции, то мы фактически построим новый язык, программировать на котором существенно проще, чем на "чистом" ЯА.

Пример 2 (макросы и обращения к процедурам)

Предположим, что имеется процедура NOD, вычисляющая наибольший общий делитель двух чисел: Z=NOD(X,Y), и что параметр X передается ее через регистр АХ, параметр Y - через ВХ, а результат Z возвращается через АХ. И пусть надо вычислить CX=NOD(A,B)+NOD(C,D). Тогда фрагмент программы, реализующий это вычисление, выглядит так:

MOV А Х , А

MOV В Х , В

CALL NOD

MOV СХ,АХ

MOV А Х , С

MOV B X , D

CALL NOD

ADD CX,AX

",ДИАЛОГ-МИФИ•

2 0 2

Программирование на языке ассемблера IBM PC Глава 1

Легко заметить, что при каждом обращении к процедуре приходится выписывать фактически одну и ту же группу команд. Так вот, чтобы их не выписывать каждый раз заново, можно эту группу команд описать как макрос, а затем его и использовать. Описание этого макроса выглядит так:

C A I X _ N 0D MACRO X , Y

MOV A X , X

MOV B X , Y

CALL NOD

ENDM

а нужный нам фрагмент программы:

CALL_N0D А , В

MOV СХ,АХ

CALL_NOD C , D

ADD CX,AX

Как видно, получилось более наглядно и короче. После же макроподстановок получится тот же текст программы, что и прежде, но построением такого текста будет заниматься уже макрогенератор, а не мы.

Отметим попутно, что именно по этому принципу построены операции вводавывода, которыми мы пользуемся в данной книге. Например, ININT - это на самом деле макрос, описывающий обращение к процедуре, которая и реализует собственно ввод числа. (Описание этих макросов и процедур будет дано в гл. 13.)

Пример 3 (макросы и блоки повторения)

При входе в процедуру, как правило, требуется спасать в стеке содержимое регистров, для чего приходится выписывать несколько команд PUSH. И если в программе много процедур, если такая группа команд встречается многократно, то имеет смысл описать ее в виде макроса. Рассмотрим, как выглядит такой макрос.

Прежде всего отметим, что по смыслу у этого макроса может быть любое число фактических параметров (разное число названий регистров). Поскольку в ЯА можно определять макросы только с фиксированным числом формальных параметров, то в подобных ситуациях обычно поступают так: макрос описывают с одним формальным параметром, но при обращении к нему (в макрокоманде) указывают через запятую нужное число фактических параметров и заключают весь их список в угловые скобки, в результате чего синтаксически получается один параметр. В теле же макроса от этого списка "отщепляют" по одному настоящему параметру и что-то с ним делают.

Далее. Макрос должен поставлять в текст окончательной программы (в макрорасширение) несколько однотипных команд PUSH г. Ясно, что здесь следует воспользоваться блоком повторения.

С учетом всего сказанного получаем такое определение нашего макроса:

Макросредства

203

SAVE MACRO REGS ;; з а п и с ь в с т е к р е г и с т р о в из списка REGS IRP R,<REGS>

PUSH R

ENDM

ENDM

Рассмотрим, как макрогенератор обрабатывает следующее обращение к нашему макросу:

SAVE <AX,SI,BP>

Прежде всего, как обычно, выписывается тело макроса с заменой формального параметра на фактический:

IRP R,<AX,SI,BP>

PUSH R

ENDM

Напомним, что уголки, в которые был заключен фактический параметр, считаются не относящимися к параметру и при макроподстановке удаляются. Однако в директиве IRP были свои уголки, именно они-то и сохранились в тексте, так что эта директива сохранила правильный синтаксис.

Получившийся текст содержит конструкцию макроязыка, а в такой ситуации, как уже отмечалось, макрогенератор продолжает свою работу. В данном случае он "раскручивает" IRP-блок, в результате чего получает уже текст на "чистом" ЯА:

PUSH АХ

PUSH

S I

PUSH

BP

который и является окончательным макрорасширением нашей макрокоманды.

11.3.5.Макросы н процедуры

Прежде чем продолжить рассказ про макросы, сравним их с процедурами.

В чем выгода от макросов? Если у нас в тексте программы многократно

встречается один и тот же

(или почти один и тот

же)

фрагмент, то

чтобы

не выписывать его каждый

раз заново, мы описываем

его

как макрос, а

затем

в нужных местах программы указываем лишь короткие макрокоманды, чем существенно облегчаем себе жизнь. Таким образом, макросы - это средство борьбы с повторяющимися действиями. Но процедуры - это тоже средство борьбы с повторяющимися действиями: многократно повторяющуюся группу команд мы можем описать как процедуру, а затем в нужным местах программы будем указывать команды вызова процедуры.

Итак, имеется два средства решения одной и той же проблемы. Естественно, возникает вопрос: какое из этих средств лучше? Чтобы ответить на этот вопрос, надо понять различие между макросами и процедурами. А оно проявляется в следующем.

",ДИАЛОГ-МИФИ•

204Программирование на языке ассемблера IBM PC Глава 1

Имакрос, и процедура описываются в программе только раз. В обоих случаях в нужных местах программы мы указываем короткие ссылки на это описание. Таким образом, с точки зрения процесса написания текста программы, особого различия между макросами и процедурами нет. Но вот после того, как мы написали программу, дальнейшая судьба макросов и процедур оказывается совершенно различной.

Воттранслированной программе процедура так и остается в единственном экземпляре, а при использовании макроса его тело подставляется во все места, где мы указали обращение к нему, т. е. макрос "размножается," и потому размеры программы увеличиваются. Таким образом, применение процедур дает выигрыш по памяти, и в этом их преимущество перед макросами.

Однако процедуры проигрывают по времени. В самом деле, при обращении к процедуре мы должны выполнить команды засылки ее параметров в те или иные регистры (либо в стек) и выполнить команду вызова процедуры. Кроме того, по окончании работы процедуры мы выполняем команду возврата из нее. На выполнение всех этих команд, естественно, затрачивается время. Если же используется макрос, то все эти команды выполнять не надо. Действительно, поскольку команды макроса подставляются в то место программы, где их надо выполнить, то команды перехода не нужны. Не нужны и команды передачи параметров, поскольку настройка на них происходит при макроподстановке, когда в теле макроса формальные параметры заменяются на фактические. Тем самым при использовании макроса мы не тратим время на переходы и передачу параметров, а поэтому и получаем выигрыш во времени.

Конечно, при замене макрокоманд на макрорасширения тратится время, но это происходит еще на этапе трансляции, до выполнения программы. Когда же программа будет оттранслирована и начнет выполняться, то здесь уже не расходуется время на переходы и передачу параметров. При использовании же процедур время на эти действия тратится как раз на этапе выполнения программы. А именно время счета программы, а не время ее трансляции, определяет эффективность программы.

Итак, процедуры дают выигрыш по памяти, а макросы - выигрыш по времени. Поэтому нельзя сказать, что процедуры лучше макросов или наоборот. Каждое из этих средств хорошо в определенных условиях, в одних случаях лучше процедуры, в других - макросы. Если в повторяющемся фрагменте программы много команд (десяток и более), то лучше описать его как процедуру. В самом деле, если этот фрагмент описать как макрос, то он затем будет многократно

размножен

и

программа сильно "разбухнет", она будет

занимать

много места

в памяти.

В

то же время процедура всегда хранится

в одном

экземпляре

и программа не "разбухает". Но если в повторяющемся фрагменте мало команд, тогда его лучше описать как макрос. Конечно, и здесь произойдет "разбухание" программы, но не так уж и сильно. В то же время, если небольшую группу команд описать как процедуру, то число вспомогательных команд по вызову ее и передаче ей параметров станет сравнимым с числом команд самой процедуры,

Макросредства 205

поэтому время ее выполнения станет чуть ли не вдвое больше времени выполнения макроса.

Итак, большие фрагменты рекомендуется описывать как процедуры, а маленькие - как макросы.

11.3.6.Определение макроса через макрос

Как известно, процедура имеет право обращаться к другой процедуре. Аналогично, при описании одного макроса можно ссылаться на другой макрос. В частности, допускается и обращение макроса к самому себе, т. е. разрешены рекурсивные макросы. Однако рекурсивные макросы встречаются на практике крайне редко, поэтому рассмотрим лишь пример нерекурсивного обращения одного макроса к другому и возникающие при этом проблемы.

Пусть макрос ARR X,N предназначен для описания массива X из N байтов:

ARR MACRO X,N

X DB N DUP(?)

ENDM

Тогда, используя его, можно определить макрос ARR2, предназначенный для описания сразу двух массивов одного и того же размера;

ARR2 MACRO Х1,Х2,К

ARR Х1,<К>

ARR Х2,<К>

ENDM

При таком макроопределении макроподстановка для макроса ARR2 проходит в два этапа, например:

Почему в теле макроса ARR2 при обращении в макросу ARR второй фактический параметр записывается в уголках? Дело в том, что если по смыслу первым и вторым параметрами макроса ARR2 могут быть только имена, то как третий параметр может быть указана достаточно сложная конструкция, например: ARR2 А,В,<25 MOD 10>. Так вот, если бы вместо записи <К> использовалась просто запись К, тогда на первом этапе макроподстановки получилась бы макрокоманда ARR А,25 MOD 10 с четырьмя операндами, а не с двумя (напомним, что при макроподстановке уголки фактического параметра отбрасываются и что в макрокомандах параметры могут отделяться как запятыми, так и пробелами). При записи же <К> уголки заставляют рассматривать эту конструкцию как один параметр: ARR А,<25 MOD 10>.

Отметим, что в ЯА допускается вложенность макроопределений, например:

ARR2 MACRO Х1,Х2,К

ARR MACRO X,N

",ДИАЛОГ-МИФИ•

206

Программирование на языке ассемблера IBM PC Глава 1

X DB N DUP(?)

ENDM

ARR X1,<K>

ARR X2, <K>

ENDM

Однако при этом надо учитывать следующее. Макрос ARR, хотя и описан внутри макроса ARR2, не локализуется в ARR2, и к нему можно обращаться вне макроса ARR2. Но ассемблер работает так, что описание внутреннего макроса он "замечает" только при первом обращении к внешнему макросу. Поэтому обращаться к макросу ARR до обращения к макросу ARR2 нельзя:

ARR

А,50

;ошибка

(имя

ARR

еще

не описано)

ARR2

В,С,100

 

 

 

 

 

ARR

D,60

;ыожно

(иыя

ARR

ухе

описано)

11-3.7. Директива LOCAL

При использовании макросов возникает неприятная проблема с метками, которыми могут быть помечены предложения тела макроса. Пусть, к примеру, имеется макрос со следующей структурой:

мMACRO

L:. . .

ENDM

и пусть в профамме имеется два обращения к этому макросу. Тогда после макроподстановок мы получим следующую картину:

Как видно, в окончательном тексте программы появились две команды, помеченные одной и той же меткой L, а это ошибка. Почему так произошло? Дело в том, что имя L не. является формальным параметром макроса и потому при макроподстановке ни на что не заменяется, а переносится в макрорасширение без всяких изменений. Вот и получается, что в окончательном тексте программы будет столько меток L, сколько было обращений к данному макросу.

Как избежать этой ошибки? Возможный вариант - включить метку L в число параметров макроса и при обращении к макросу указывать различные фактические метки. Однако это плохое решение, поскольку метки, которыми метятся команды тела макроса, - это чисто внутреннее дело макроса, и для того, кто будет пользоваться этим макросом, нет никакого дела до этих меток, придумывать имена для таких внутренних меток - это лишняя работа.

Макросредства 207

Учитывая это, в ЯА предложено иное, более удобное решение данной проблемы. Оно заключается в том, что после заголовка макроса (директивы MACRO) надо указать специальную директиву макроязыка:

LOCAL v l , vk ( k > = l )

где vi - имена, используемые в макроопределении (обычно это метки). Тогда при макроподстановке макрогенератор будет заменять эти имена на специальные имена вида ??хххх, где хххх - четырехзначное шестнадцатеричное число, т. е. на имена ??0000, ??0001 и так далее до ??FFFF. Правила такой замены следующие.

Макрогенератор запоминает номер, который он использовал в последний раз;

пусть это был номер п. Когда макрогенератор встречает

обращение к

макросу,

в котором имеется директива LOCAL, то он ставит в

соответствие

именам,

перечисленным в этой директиве, специмена с очередными номерами: специмя ??(п+1) для vl, специмя ??(п+2) для v2 и т. д., а затем при макроподстановке заменяет каждое вхождение vi на одно и то же специмя ??(n+i). Когда макрогенератор встретит новое обращение к этому же или другому макросу, где есть директива LOCAL, то он будет уже использовать специмена с последующими номерами: n+k+1 и т. д. Поэтому в разных макрорасширениях появятся разные специмена, совпадений не будет.

Рассмотрим конкретный пример. Опишем макрос, вычисляющий остаток от деления одного натурального числа на другое с помощью вычитания:

MD

MACRO R1,R2

; r l : = r l

mod r2

( r l , r 2 - р е г и с т р ы , б е з з н а к а )

 

LOCAL М,М1

 

 

 

М:

CMP R1,R2

; ; w h i l e

r l > = r 2

do r l : = r l - r 2

 

JB Ml

 

 

 

 

SOB R1,R2

 

 

 

J M P M

ML:

ENDM

и предположим, что в последний раз макрогенератор использовал для специмен номер 0016. Тогда два очередных обращения к макросу MD будут обработаны следующим образом:

",ДИАЛОГ-МИФИ•

208 Программирование на языке ассемблера IBM PC Глава 1

Таким образом, в разных макрорасширениях появляются разные метки, и тем самым проблема с одинаковыми метками снимается.

Отметим, что директиву LOCAL можно указывать только после директивы MACRO (но зато любое число раз) и нигде более и что директива LOCAL не переносится в макрорасширение. Кроме того, следует учитывать, что специмена ??0017 и т. п. - это обычные имена, и, вообще говоря, мы можем их использовать сами, однако в силу их особой роли не рекомендуется применять эти имена в ином качестве.

11J.8. Директива EXITM

Рассмотрим еще одну директиву макроязыка: EXITM

У этой директивы нет операндов. Ее можно использовать только внутри макроопределений и блоков повторения, т. е. внутри конструкций макроязыка, оканчивающихся директивой ENDM. Встретив директиву EXITM, макрогенератор завершает обработку ближайшего объемлющего макроопределения или блока повторения.

Например, при макроопределении

A MACRO К

REPT К

DB О

EXITM

ENDM

DW ?

ENDM

макрокоманда A 5 будет заменена на следующий текст:

DB О

DW ?

Здесь макрогенератор, создавая первую копию тела блока повторения, перенесет предложение DB 0 в макрорасширение, а затем, встретив EXITM, полностью завершит обработку этого блока, но не покинет тело макроса - он "перескочит" за ближайшую директиву ENDM, т. е. на предложение DW ?.

Более содержательные примеры на директиву EXITM будут приведены позже (они требуют знания условных директив). Пока лишь отметим, что эта директива используется тогда, когда при выполнении некоторого условия надо досрочно (не доходя до ENDM) прекратить макроподстановку или "раскрутку" блока повторения.

11.3.9.Переопределение и отмена макросов

Вотличие от других объектов программы на ЯА, макросы можно переопределить или уничтожить.

Если в тексте программы описать макрос с именем, которым ранее был обозначен другой макрос, то с этого момента прежний макрос считается уничтоженным, а новый макрос - действующим. Например:

Макросредства 209

Макрос можно и просто уничтожить, не определяя новый макрос с тем же именем, для чего надо воспользоваться следующей директивой:

PURGE <имя макроса> {,<имя макроса>>

После этой директивы все макросы, имена которых в ней перечислены, считаются несуществующими. Например, после директивы

PURGE A, ININT

к макросам А и IN1NT уже нельзя обращаться.

11.4.Условное ассемблирование

Если макросы и блоки повторения позволяют избежать многократного выписывания в исходном тексте программы повторяющихся фрагментов, то рассматриваемое в данном разделе средство макроязыка - условное ассемблирование - удобно при многократных прогонах программы. Оно дает возможность в исходном тексте держать несколько вариантов одного и того же участка программы, а при каждом ее прогоне оставлять в окончательном тексте только один из них. Какой именно вариант будет оставлен, зависит от тех или иных условий, которые автор программы задает перед прогоном. Выгода от такой условной сборки окончательного текста программы заключается в том, что автор программы не должен перед каждым ее прогоном вручную редактировать ее текст (это чревато ошибками и требует значительного времени), а возлагает эту работу на макрогенератор (он работает без ошибок и быстрее).

Участок программы, затрагиваемый условным ассемблированием, должен записываться в виде так называемого IF-блока:

<1Г - директива>

 

<1Г - директи»а>

«фрагмент - 1>

или

«фрагмент -1>

ELSE

 

ENDIF

«фрагмент - 2>

 

 

ENDIF

 

 

",ДИАЛОГ-МИФИ•

 

 

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]