В.Г. Баула - Введение в архитектуру ЭВМ и системы программирования (975817), страница 31
Текст из файла (страница 31)
cmpax macro X
local L
cmp ax,X
jge L
mov ax,X
L:
endm
Поясним работу макроопределения maxn, однако сначала, как мы обещали ранее, нам надо существенно уточнить правила передачи фактического параметра (строки символов) на место формального параметра. Дело в том, что некоторые символы, входящие в строку-фактический параметр, являются для макропроцессора служебными и обрабатываются по-особому (такие символы называются в нашем макроязыке макрооператорами). Ниже приведено описание наиболее интересных макрооператоров, полностью их необходимо изучить по учебнику [5].
-
Если фактический параметр заключён в угловые скобки, то они считаются макрооператорами, их обработка заключается в том, что они отбрасываются при передаче фактического параметра на место формального.
-
Символ восклицательного знака (!) является макрооператором, он удаляется из фактического параметра, но при этом блокирует (иногда говорят – экранирует) анализ следующего за ним символа на принадлежность к служебным символам (т.е. макрооператорам). Например, фактический параметр <ab!!+!>> преобразуется в строку ab!+>, именно эта строка и передаётся на место формального параметра. Это один из способов, как можно передать в качестве параметров сами служебные символы.
-
В том случае, если комментарий начинается с двух символов ;; вместо одного, то это макрокомментарий, такой комментарий не переносится в макрорасширение.
-
Символ & является макрооператором, он удаляется макропроцессором из обрабатываемого предложения (заметим, что из двух следующих подряд символов & удаляется только один). Данный символ играет роль лексемы – разделителя, он позволяет выделять в тексте имена формальных параметров макроопределения и переменных периода генерации. Например, пусть в программе есть такой макроцикл
K=1
irp i,<l,h>
K=K+1
mov a&i,X&K&i
endm
После обработки этого макроцикла Макрокпроцессор подставит на это место в текст программы следующие строки:
mov al,X2l
mov ah,X3h
-
Символ % является макрооператором, он предписывает макропроцессору вычислить следующее за ним арифметическое выражение и подставить значение этого выражения вместо знака %. Например, после обработки предложений
N equ 5
K=1
M equ %(3*K+1)>N
Будут получены предложения
N equ 5
M equ 4>N
Разберём теперь выполнение макроопределения maxn для макрокоманды
maxn <-13,,bx,Z>
На место формального параметра X будет подставлена строка символов -13,,bx,Z. Таким образом, макроцикл принимает следующий вид
irp i,<-13,,bx,Z>
ifnb <i>
cmpax i
endif
endm
В теле этого макроцикла располагается условный макрооператор с именем ifnb, он проверяет свой параметр и вырабатывает значение true, если этот параметр не является пустой строкой символов. Таким образом, получается, что в теле макроцикла выполняется условный макрооператор, который только для непустых элементов из списка цикла вызывает вспомогательное макроопределение cmpax. Единственным назначением этого вспомогательного макроопределения является локализация в нём метки L, которая таким образом получает уникальное значение для каждого параметра из списка цикла.
Упражнение. Напишите макроопределение maxn без использования вспомогательного макроопределения. Надо сразу сказать, что для этого требуется хорошее знание языка Ассемблера.
Макроопределения с переменным числом параметров являются достаточно удобным средством при программировании многих задач. Аналогичный механизм (но с совершенно другой реализацией) есть в языке высокого уровня С, который допускает написание функций с переменным числом фактических параметров. Правда, для функций языка С фактических параметров должно быть не менее одного, и значение этого первого параметра должно как-то задавать общее число фактических параметров. Вы будете изучать язык С в следующем семестре.
Теперь мы познакомимся с использованием макросредств для настройки макроопределения на типы передаваемых ему фактических параметров. Здесь имеется в виду, что, хотя с точки зрения макропроцессора фактический параметр – это просто строка символов, но с точки зрения Ассемблера у этого параметра может быть тип. Например, в Ассемблере это может быть длинная или короткая целая переменная, константа и т.д. и ясно, что для обработки операндов разных типов требуются и различные команды.
Как мы помним, для языка Паскаль должно соблюдаться строгое соответствие между типами фактических и формальных параметров процедур. А как быть программисту, если, например, ему надо написать функцию для поиска максимального элемента массива, причём необходимо, чтобы в качестве фактического параметра этой функции можно было бы передавать массивы разных типов (с целыми, вещественными, символьными, логическими и т.д. элементами)? На языках высокого уровня хорошо решить эту задачу практически невозможно.71
Сейчас мы увидим, что макросредства предоставляют программисту простое и элегантное решение, позволяющее настраивать макроопределение на тип фактических параметров. Предположим, что нам в программе необходимо суммировать массивы коротких и длинных целых чисел. Для реализации такого суммирования можно написать макроопределение, например, с таким заголовком:
SumMas macro X,N
В качестве первого параметра этого макроопределения можно задавать массивы коротких или длинных знаковых целых чисел, длина массива указывается во втором параметре. Другими словами, первый операнд макрокоманды SumMas может быть формата m8 или m16, а второй – формата m16 или i16. Сумма должна возвращаться на регистре ax (т.е. наше макроопределение – в некотором смысле функция). Допускается, чтобы второй параметр был опущен, в этом случае по умолчанию считается, что в массиве 100 элементов. Для простоты наше макроопределение не будет сохранять и восстанавливать используемые регистры, а переполнение при сложении будем игнорировать. Кроме того, не будем проверять допустимость типа второго параметра, например, передачу в качестве второго параметра короткого регистра r8 или какой-нибудь метки программы. Ниже показан возможный вариант такого макроопределения.
SumMas macro X,N
Local K,L
ifb <X>
%out Нет массива!
.err
exitm
endif
ifb <N>
%out Берём длину=100
mov cx,100
else
mov cx,N
endif
if type X LT 1 or type X GT 2
%out Плохой тип массива!
.err
exitm
endif
K=word
if type X EQ byte
K=byte
endif
lea bx,X
xor dx,dx; Сумма:=0
if K EQ byte
L: mov al,[bx]
cbw
add dx,ax
else
L: add dx,[bx]
endif
add bx,K
loop L
mov ax,dx
endm
Как видим, наше макроопределение настраивается на тип переданного массива и оставляет в макрорасширении только команды, предназначенные для работы с элементами массива именно этого требуемого типа. Заметьте также, что вся эта работа по настройке на нужный тип параметров производится до начала счёта (на этапе компиляции), то есть на машинном языке получается эффективная программа, не содержащая никаких лишних команд.
Упражнение. Объясните, для чего предназначено показанное ниже макроопределение и как к нему следует обращаться:
BegProc macro R
push bp
mov bp,sp
irp i,<R>
push i
endm
endm
В качестве ещё одного примера рассмотрим следующую задачу. Пусть программисту на Ассемблере необходимо много раз вызывать различные процедуры со стандартным соглашением о связях, передавая им каждый раз досточно большое количество параметров. Естественно, программист хочет автоматизировать процесс вызова процедуры, написав макроопределение, которой позволяет вызывать процедуры почти так же компактно, как и в языках высокого уровня. Например, пусть в Паскале есть описание процедуры с заголовком
Procedure P(var X:Mas; N:integer; var Y:integer);
Такую процедуру можно, например, вызвать оператором процедуры P(X,400,Y) . В Ассемблере для фактических параметров, описанных, например, так:
X dw 400 dup (?)
Y dw ?
вызов процедуры, как мы знаем, будет, например, производится последовательностью команд:
mov ax,offset X
push ax
mov ax,400
push ax
mov ax,offset Y
push ax
call P
Для автоматизации вызова процедур напишем такое макроопределение:
CallProc macro Name,Param
irp i,<Param>
mov ax,i
push ax
endm
call Name
endm
Вот теперь вызов нашей процедуры можно производить одной макрокомандой
CallProc P,<<offset X>,400,<offset Y>>
Разумеется, это выглядит не так красиво, как в Паскале, но здесь уж ничего не поделаешь, хотя для любителей макрооператоров Ассемблера можно предложить и альтернативный вызов нашего макроопрелеления, не перегруженный угловыми скобками:
CallProc P,<offset! X,400,offset! Y>
Как бы то ни было, главная наша цель достигнута – вызов процедуры стал производиться в одно предложение Ассемблера. Заметим, что пустой список фактических параметров можно, как и в Паскале, полностью опускать, например
CallProc F
Посмотрим теперь, как разработчик нашего макроопределения сможет проконтролировать, что в качестве первого параметра передано непустое имя некоторой процедуры (вообще говоря, метки). Проще всего проверить, что переданный параметр не пустой, как мы уже знаем, это можно выполнить, используя условный макрооператор
ifb <Name>
%out Пустое имя процедуры
.err
exitm
endif
Несколько сложнее обстоит дело, если наш программист захочет проверить, что в качестве первого параметра передано именно имя, а не какая-нибудь недопустимая строка символов. Поставленную задачу можно решить, например, с помощью такого фрагмента на Макроассемблере:
Nom=1; Номер символа в имени
irpc i,<Name>
Err=1; Признак ошибки в очередном символе имени
if Nom EQ 1
irpc j,<abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOQPRSTUVWXYZ_?@#$>
ifidn <i>,<j>
Err=0
endif
endm
else
irpc j,<abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOQPRSTUVWXYZ_?@#$0123456789>
ifidn <i>,<j>
Err=0
endif
endm
endif
if Err EQ 1
exitm; Выход из цикла irpc
endif
endm
if Err EQ 1
%out Плохой символ i в имени Name
.err
exitm; Выход из макроопределения
endif
Для анализа символов фактического параметра на принадлежность заданному множеству символов мы использовали макроцикл irpc. Выполнение этого макроцикла очень похоже на выполнение макроцикла irp, за исключением того, что параметру цикла каждый раз присваивается очередной один символ, входящий в список цикла. В нашем примере в списке цикла мы указали в первом операторе irpc все символы, с которых может начинаться имя в Ассемблере (это латинские буквы и некоторые символы, приравнивающиеся к буквам). Во втором операторе irpc из нашего примера мы к буквам просто добавили 10 цифр. Заметим, что, как уже отмечалось ранее, каждое предложение надо записывать в одну строку, мы разбили каждый из макроциклов irpc на две строки исключительно для удобства чтения нашего примера, что, конечно же, будет классифицировано Ассемблером как синтаксическая ошибка.
Итак, теперь мы убедились, что в качестве первого параметра наше макроопределение получило некоторое имя. Но является ли полученное нами имя именно именем процедуры (или, в более общем случае, именем команды)? Чтобы выяснить это, в наше макроопределение можно вставить проверку типа полученного имени, например, так:
if type Name NE -1 and type Name NE -2
%out Name не имя процедуры!