|
Глава 1. СТРУКТУРНОЕ ПРОГРАММИРОВАНИЕ
1: ИНСТРУМЕНТАЛЬНЫЕ СРЕДСТВА СТРУКТУРНОГО ПРОГРАММИРОВАНИЯ
Потребность в короткой записи операторов
Введение в МАКРОСЫ
Условное ассемблирование
Условное ассемблирование и МАКРОСЫ
Структурные операторы управления в языке Ассемблер
Макросы данных
Макросы генерации программного кода
Применение директивы STRUC
Когда программисты-фанатики собираются в своем кругу для об-
суждения тайн структурного программирования, разговор обычно кон-
центрируется на небольшом наборе конструкций языка типа
IF-THEN-ELSE. Приверженец языков Паскаль или Си будет читать лек-
цию о преимуществах языков высокого уровня по сравнению с языками
ассемблерного типа. Вероятно, будут приведены горячие аргументы
по поводу использования оператора GOTO. Несмотря на все предыду-
щие обсуждения, ясно, что сказано далеко не все. В действитель-
ности, подобное обсуждение фокусируется только на структурном
программировании. Как Вы скоро увидите, структурное программиро-
вание возможно на любом языке. Модные структуры управления языков
высокого уровня поддерживают даже некоторые ассемблеры. Одним из
таких ассемблеров является Макро Ассемблер фирмы Майкрософт для
операционной системы MS-DOS, широко известный под названием MASM.
Потребность в короткой записи операторов
Прежде чем представить структуры управления высокого уровня в
языке Ассемблера мы рассмотрим некоторые преимущества языков вы-
сокого уровня. Так или иначе все заканчивается на уровне ассемб-
лера. Что же выигрывается от использования языка высокого уровня?
Возможность выражать программистскую идею в форме, легко пони-
маемой программистом или специалистом. Предположим, что каждому
оператору ассемблера в большей или меньшей степени соответствует
одна машинная команда.С другой стороны, один оператор языка высо-
кого уровня может расширяться до десятков или даже сотен машинных
инструкций. (Тот, кто сомневается в расширении до сотен, может
проверить это, проанализировав работу вычислительной подпрограм-
мы на языке Фортран, имеющей множество аргументов).
На Рис.1-1 показан фрагмент одной и той же программы, реализо-
ванный на Фортране и языке Ассемблера 8086. Этот фрагмент вычис-
ляет сумму 1... NUM для данного NUM. Нет сомнений, что программа
на ассемблере прежде всего может быть оптимизирована с целью
Фортран Aссемблер
sum = 0 mov sum,0
DO 100 I = 1, NUM mov ax,1
100 SUM =SUM +I loop1: cmp ax,num
jg loop1_end
add sum,ax
inc ax
jmp loop1
loop1_end:
Рис. 1-1. Сравнение Фортрана с Ассемблером
- 1-2 -
уменьшения или количества выполняемых команд, или времени выпол-
нения программы. Но вне зависимости от того, как к этому подойти,
легче написать программу на Фортране, чем на Ассемблере. Для на-
писания программы на Ассемблере должно быть принято гораздо боль-
ше решений. В силу значительной сложности работы по программиро-
ванию на ассемблере ошибки кодирования более вероятны. Я могу
быть уверен, что программа на Фортране будет выполняться превос-
ходно, но я не могу сказать этого о программе на ассемблере. По-
чему возникают такие сомнения? Потому что каждая строка программы
на Фортране представляет собой законченную мысль, в то время как
ассемблерная программа требует для реализации той же мысли мно-
жества строк.
Короче говоря, использование конструкций высокого уровня при-
водит к облегчению процесса программирования и повышению надеж-
ности программ. Такие конструкции делают программирование менее
сложным, что позволяет программисту сконцентрироваться на логике
программы. Естественно, что программисты хотели бы быть уверены в
правильности результатов своей работы. Инструментальные средства,
поддерживающие такую уверенность, приводятся в нижеследующих раз-
делах.
Введение в МАКРОСЫ
Таким образом, программирование на языке Ассемблера может быть
значительно облегчено, если иметь возможность создавать "стеног-
рамму" часто используемых операторов. MASM обеспечивает эту воз-
можность через средства макро. Макро представляют собой "суперко-
манды", которые разгружают MASM от части лишней и часто
повторяющейся работы по обработке ассемблерной программы. При по-
мощи макросов программисты определяют блоки ассемблерных операто-
ров, а затем, используя конкретные ссылки, указывают MASM на
включение соответствующих блоков в ассемблерную программу. В этой
главе мы рассмотрим некоторые из таких макросов и понемногу ра-
зовьем Ваши способности по написанию собственных инструментариев.
Все это позволит Вам соединить скорость выполнения ассемблерной
программы с мощностью языка высокого уровня.
Для создания и использования макро необходимо выполнить 2 ша-
га:
Шаг 1. Определение макро
;; Определить "Требуемую функцию" типа @DosCall
@DosCall MACRO
int 21h ;для выполнения функции обра-
ENDM ;титься к MS-DOS
Шаг 2. Использование макро
@DosCall <--- вызов макро
В листинге появится следующее:
@DosCall <--- вызов макро
1 int 21h ;для выполнения функции обра-
;титься к MS-DOS
При ассемблировании программы оператор DosCall заменяется на
оператор int 21h, включая комментарий. Файл листинга содержит
строку DosCall как ссылку, однако объектный файл содержит только
- 1-3 -
код для инструкции int 21h. Такая операция известна под названием
"подстановка макро" или " расширение макро".
Заметьте, что в предыдущем примере ассемблер вставил в файл
листинга символ, обозначающий код расширенного макро. В MASM вер-
сии 4 и выше "1" помещается в строки, принадлежащие первому уров-
ню макрорасширения, "2" используется для второго уровня и т.д. В
MASM версии 3 и предыдущих версий все строки макрорасширения вне
зависимости от уровня помечаются символом плюс (+).
При обработке ассемблером ссылка на макро заменяется на прог-
раммный код, который это макро представляет. Макро не вырабатыва-
ет команду СALL (вызвать), обращенную к коду макро, хотя ссылки
на макро порой и используют такой путь.
Подобно другим конструкциям в программировании макросы должны
следовать строгим правилам. Форма описания макроса следующая:
mname MACRO argument_list
.
. <--- тело макрокода
.
ENDM
Имя макро определяется как mname, а argument_list представляет
собой список аргументов, разделенных запятыми. Если макро не со-
держит аргументов (как в нашем примере с @DosCall), список аргу-
ментов может быть пуст.
Выше был приведен простейший пример. Если это было бы все, что
умеет делать макро, то тогда оно было бы довольно примитивным об-
разованием. К счастью, макросы можно подгонять к конкретным усло-
виям применения, используя секцию аргументов. Следующее макро яв-
ляет собой пример подобной настройки.
;; Определить "Печать символа" как PrintChr
@PrintChr MACRO char
mov ah,05
mov dl,&char
@DosCall
ENDM
И теперь при использовании макро мы пишем:
@PrintChr 'A' <--- вызов макро,
и в нашем листинге появляется следующее:
@PrintChr 'A' <--- вызов макро
1 mov ah,05
1 mov dl,'A'
2 int 21h ;для выполнения функции обратиться к MS-DOS
Конструкция "&char" в макроописании была заменена после вызова
макро на "A". (Да, мы ссылаемся на макро, как если бы стоял вызов
call. Это удобно, особенно если вспомнить, что команда CALL в яв-
ном виде не используется.) Цифра, появляющаяся в начале строки,
представляет собой способ, при помощи которого MASM сообщает
программисту, что текущий код является результатом макрорасшире-
ния. Также заметим, что макро @PrintChr содержит ссылку на ранее
определенное макро @DosCall, которое расширяется в оператор
int 21h, его представляющий. MASM продолжает "раскручивать" вы-
- 1-4 -
зовы макро до такого уровня, до которого они вложены, пока не пе-
реполнится область памяти таблицы символов. Вложенность являет
собой другой способ сообщения, что макро может вызвать макро, ко-
торое в свою очередь может вызвать следующее макро и т.д.
Имя char в макро @PrintChr называется формальным аргументом.
Всякий раз, когда формальный аргумент char появляется в макро, он
заменяется на значение, использованное при вызове макро. В приме-
ре с @PrintChr замена char означает, что все появления сhar в
макро заменяются на "A".
Заметим, что любое имя, выбранное для формального аргумен-
та,используется исключительно для этого аргумента. Таким образом,
если Вы для формального аргумента выбрали имя AX, Вы не можете в
данном макро ссылаться на регистр AX!
Аналогичное предупреждение действует и для именования собс-
твенно макро. Как только для описания макро Вы выбрали имя add,Вы
найдете, что все ссылки на код операции ADD будут вырабатывать в
данной программе расширение макро add. При желании, таким обра-
зом, можно изменять директивы MASM. Однако очень важно не созда-
вать для имен конфликтные ситуации.
Символ "&" перед char в макро @PrintChr используется для до-
бавления в строку mov dl, значения char. Символ "&" не нужен для
раскрутки формального аргумента, что происходит и так, а нужен
для сообщения MASM, что char является формальным аргументом, а не
частью более длинной строки "mov dl,char". Как показано в следую-
щем примере, оператор "&" особенно важен, если формальные аргу-
менты содержатся в длинных строках.
Макроописание Макрорасширение
@Example MACRO arg @Example Y
mov dl,arg 1 mov dl,y <-правильно
mov dl,&arg 1 mov dl,y <-правильно
mov dl,argZ 1 mov dl,argZ
mov dl,&argZ 1 mov dl,argZ
mov dl,arg&Z 1 mov dl,YZ <-правильно
mov dl,Xarg 1 mov dl,Xarg
mov dl,X&arg 1 mov dl,XY <-правильно
mov dl,XargZ 1 mov dl,XargZ
mov dl,X&argZ 1 mov dl,XargZ
mov dl,Xarg&Z 1 mov dl,XargZ
mov dl,X&arg&Z 1 mov dl,XYZ <-правильно
ENDM
Строго говоря, в макро @PrintChr символ "&" не требуется. MASM
имеет возможность определить, что char - формальный аргумент, так
как после запятой он присутствует в одиночестве. Тем не менее,
это хорошая привычка использовать символ "&", даже когда он не
требуется, так как он выделяет формальный аргумент при чтении
макро и проясняет для MASM, что имелось в виду.
Метки типа LOCAL
До сих пор макросы, использованные нами, предназначались лишь
для генерации простых ассемблерных команд. Теперь предположим,
что мы хотим создать макро, которое выбирает меньшее из двух чи-
сел и помещает результат в какую-то ячейку. Такое макро может
выглядеть, например, так:
- 1-5 -
min MACRO result,first,second
mov &result,&first
cmp &first,&second
jl order_ok
mov &result,&second
order_ok:
ENDM
Когда мы вызываем макро min, оно вырабатывает правильный код,
однако имеется одна проблема: хотя макро вычисляется превосходно,
оно может быть использовано лишь единожды. Так как метка order_ok
может быть определена в программе только один раз, при использо-
вании данного макро в двух местах программы MASM распознает мно-
жественное определение символа.
Чтобы в добавление к другим параметрам разрешить указание па-
раметра метки, мы можем выполнить небольшое изменение макро:
min MACRO result,first,second,order-ok
mov &result,&first
cmp &first,&second
jl &order_ok
mov &result,&second
оrder_ok&:
ENDM
При вызове нового макро min, показанного в следующем номере,
мы можем указать имя, которое будет использоваться для метки пе-
рехода. Теперь макро min может быть использовано всякий раз, ког-
да это необходимо, так как каждый раз метке перехода будет прис-
ваиваться новое имя. Однако действительное имя не имеет для нас
никакого значения, ибо метка является собственностью функции min.
min ax,bx,cx,jmp1 <- вызов макро
1 mov ax,bx
1 cmp bx,cx
1 jl jmp1
1 mov ax,cx
1 jmp1:
Такой способ создания нового имени при каждом вызове макро min
является лучшим. Именно для этой цели MASM имеет директиву LOCAL
(локальный). Когда MASM встречает LOCAL, для стоящего рядом имени
создается уникальная метка. Другой способ заключается в помещении
параметра LOCAL в список параметров MACRO, но при этом MASM про-
изводит присваивание действительного аргумента. Предупреждение:
операторы LOCAL всегда должны помещаться сразу же после строки
именования MACRO! После включения директивы LOCAL новое макро min
выглядит так:
min MACRO result,first,second
LOCAL order_ok
mov &result,&first
cmp &first,&second
jl order_ok
mov &result,&second
order_ok:
ENDM
- 1-6 -
Теперь, когда мы снова вызовем макро min, образуется листинго-
вый файл, как показано в следующем примере. Значение order_ok бу-
дет заменено на ??0000. Каждый раз при вызове макро order_ok за-
меняется на новое значение, вырабатываемое MASM.
min ax,bx,cx ;первый вызов
1 mov ax,bx
1 cmp bx,cx
1 jl ??0000
1 mov ax,cx
1 ??0000:
min ax,bx,cx ;второй вызов
1 mov ax,bx
1 cmp bx,cx
1 jl ??0001
1 mov ax,cx
1 ??0001:
Конечно, остается вероятность возникновения конфликта меток,
если Вы решите использовать метки, начинающиеся с ??. Если Вы ус-
траните использование меток, начинающихся с ??, то Вы сможете вы-
зывать макро min столько раз, сколько захотите.
Использование меток LOCAL не ограничивается только переходами
по адресам. Метки LOCAL могут также использоваться с данными, как
показано в следующем макросе. В этом случае макрос используется
для вставки текстовых строк в сегмент данных и последующего соз-
дания ссылки на них в сегменте кода.
Сравнивая исходный текст с макрорасширением в Листинге 1-1,
можно увидеть, насколько удобно использовать макрос. Листинг 1-1
также содержит некоторые полезные макросы, облегчающие решение
задачи по написанию программ .ЕХЕ. Однажды определив эти макросы,
Вы можете не беспокоиться за правильность синтаксиса программ
.ЕХЕ!
Листинг 1-1. Программа Hello World
-------------------------------------------------------------
;********************************************************
; С Е К Ц И Я М А К Р О О П И С А Н И Я
;********************************************************
;
@DosCall MACRO
int 21h ;вызвать функцию MS-DOS
ENDM
;
@InitStk MACRO ;определить размер стека
stk_seg SEGMENT stack
DB 32 dup ('stack ')
stk_seg ENDS
ENDM
;
@InitPrg MACRO segment ; инициализировать сегмент
ASSUME ds:segment ; данных
start: ; основная точка входа
mov ax,segment
- 1-7 -
mov ds,ax ;установить сегмент данных
mov es,ax ;установить внешний сегмент
ENDM
;
@Finis MACRO
mov ax,4C00h ;завершить процесс
@DosCall
ENDM
;
@DisStr MACRO string ;отобразить строку памяти
mov dx,offset string
mov ah,09h
@DosCall
ENDM
;
@TypeStr MACRO string ;определить и отобразить строку
LOCAL saddr ;установить локальную метку
cod_seg ENDS ;завершить сегмент кода
dat_seg SEGMENT ;перейти к сегменту данных
saddr DB string,'$' ;определить строку в сегм.данных
dat_seg ENDS ;завершить сегмент данных
cod_seg SEGMENT ;вернуться к сегменту кода
@DisStr saddr ;отобразить строку
ENDM
;
;
;********************************************************
; П Р О Г Р А М М Н А Я С Е К Ц И Я
;********************************************************
;
@IniStk ;установить стек
cod_seg SEGMENT ;определить сегмент кода
main PROC FAR ;главная (одна) процедура
ASSUME cs:cod_seg ;назначить сегм.кода рег.CS
@InitPrg dat_seg ;инициализ-ать сегмент кода
@TypeStr 'Hello world!' ;выдать приветствие
@Finis ;закончить программу
main ENDP ;завершить процедуру
cod_seg ENDS ;завершить сегмент кода
END start ;завершить программу и ...
;определить адрес пуска
-------------------------------------------------------
Программу можно вводить точно так, как она приведена, а затем
ее ассемблировать и выполнять. Слова "Hello world!" подлежат
отображению на экране.Сам по себе результат не очень выразителен,
однако, если используемый макрос сохранен в файле include (вклю-
чить), написание .EXE-программ значительно облегчается. Давайте
посмотрим на распечатку расширения программы, приведенного в Лис-
тинге 1-2.
- 1-8 -
Листинг 1-2. Макрорасширение программы Hello World
-------------------------------------------------------------
;********************************************************
; П Р О Г Р А М М Н А Я С Е К Ц И Я
;********************************************************
;
; @InitStk ;установить стек
1 stk_seg SEGMENT stack
1 DB 32 dup ('stack ')
1 stk_seg ENDS
cod_seg SEGMENT ;определить сегмент кода
main PROC FAR ;главная (одна) процедура
ASSUME cs:cod_seg ;назначить сегм.кода рег.CS
@InitPrg dat_seg ;инициализ-ать сегмент данных
1 start: ;главная точка входа
1 mov ax,dat_seg
1 mov ds,ax ;установить сегмент данных
1 mov es,ax ;установить внешний сегмент
@TypeStr 'Hello World!' ;выдать приветствие
1 cod_seg ENDS ;приостановить сегмент кода
1 dat_seg SEGMENT ;перейти к сегменту данных
1 ??0000 DB 'Hello world!,'$' ;определить строку
1 dat_seg ENDS ;приостановить сегмент данных
1 cod_seg SEGMENT ;вернуться к сегменту кода
2 mov dx,offset ??0000
2 mov ah,09h
3 int 21h ;вызвать функцию MS-DOS
@Finis ;завершить программу
1 mov ax,4C00h ;завершить процесс
2 int 21h ;вызвать функцию MS-DOS
main ENDP ;закончить процедуру
cod_seg ENDS ;закончить сегмент кода
END start ;закончить программу ...
Прежде всего необходимо заметить, что используемый локальный
адрес (saddr) в @TypeStr отлично работает как метка оператора
данных. При связывании меток с данными не используйте двоеточие
(:). Далее посмотрим, как макрорасширение использует зарезервиро-
ванное слово SEGMENT (сегмент) в макро @InitPrg. Нет проблем!
Вспомните, что имена формальных аргументов в списке аргументов
перекрывают все другие описания MASM.
Обратите внимание, что некоторые строки не включены в листин-
говый файл. Например, оператор ASSUME ds:data_seg из @InitPrg
опущен. Оператор был отассемблирован, но MASM подавил вывод его
расширения.
Все это произошло по причине специфики обработки макросов. По
умолчанию, исходные строки, не вырабатывающие исполнительного ко-
да, в листинге подавляются. Оператор ASSUME является директивой
MASM, которая не вырабатывает собственного кода; таким образом,
он в листинге отсутствует. С другой стороны, директивы завершения
сегмента ENDS приводятся в листинге, хотя программный код не вы-
рабатывают. Есть в MASM тайны, над которыми всем нам стоит пораз-
мышлять.
Представленную программу не следует рассматривать, как эталон
хорошего программирования. Хотя идея использования макросов в
вводной и заключительной части .EXE-программ и замечательна,
включение имен "важных" символов в сами макросы применяется ред-
ко. Если имя сегмента данных отличается от dat _seg, в программе
может возникнуть нежелательная конфликтная ситуация. Например,
когда макро @TypeStr должно передать имя dat_seg в качестве аргу-
- 1-9 -
мента или макро @InitPrg полагает, что сегмент данных называется
dat_seg.
Директивы листинга макро
Если вы хотите увидеть полный листинг макро, поместите в файл
с ассемблерной программой директиву MASM .LALL. Затем получите
файл .LST и сравните его с первоначальным листингом нашего приме-
ра. Теперь оператор ASSUME ds:data_seg будет в листинге. Для из-
менения режима листинга на обратный используйте директиву .XALL.
Она вернет MASM в режим, устанавливаемый по умолчанию. Если Вы
хотите подавить все макрорасширения, используйте директиву .SALL.
Макробиблиотеки
Термин "макробиблиотека" не совсем верен. В действительности,
макробиблиотеки совсем не то, что под этим могли бы понимать
программные средства LINK /редактор/ и LIB /обработчик библиотек/
фирмы Майкрософт.Макросы должны подключаться во время компиляции,
так как они представляют собой директивы для MASM и только для
MASM. Средства LINK и LIB не знают, что делать с ними. Вместо
этого макробиблиотеки являют собой файлы типа include (включить).
Они могут определяться в отдельном файле, называемом MYLIB.MAC
или STANDARD. MLB или как-нибудь еще (Вы можете выбрать любое
допустимое имя), и подключаться при ассемблировании посредством
помещения в исходный текст программы директивы include. Например,
INCLUDE C:\ASМ\LIB\STANDARD.MLB \*
Правила написания имени и указания накопителя те же, что и для
всей системы. В файле листинга строки, полученные из файла
include, начинаются с буквы "C", равно как строки макрорасширения
начинаются с плюса "+" (в версиях MASM ниже 4.0) или номера уров-
ня расширения. Конечно, если у Вас большая библиотека и Вы не хо-
тите загромождать файл .LST макроописаниями, при помощи директивы
.XLIST "выключите" листинг перед include, а затем "включите" его
обратно (после include), применяя директиву .LIST.
Использование макробиблиотек обосновывает введение следующих
макродиректив. Хотя довольно редко сначала определяют макро в
программе, а затем отменяют такое определение (Вы бы его скорее
просто уничтожили!), для использования нескольких макроопределе-
ний Вы вполне можете подключить макробиблиотеку. Оставшиеся мак-
роопределения занимают значительное пространство памяти в таблице
символов MASM и в области памяти макро. Вернуть эту память можно
при помощи директивы PURGE (очистить). PURGE позволяет изъять
описания указанных макро. Для изъятия макроописаний предыдущего
примера следует выдать директиву:
PURGE @DosCall,@InitStk,@InitPrg,@Finis,@DisStr,@TypeStr
Она очищает все пространство памяти, занятое макроописаниями,
и позволяет нам использовать его для других целей.
- 1-10 -
Макродиректива повторения - REPT
MASM обеспечивает возможность повторять блок макрокода. Су-
ществует три варианта повтора, причем каждый из них имеет свое
особое предназначение.
В качестве первого примера предположим, что мы хотим создать в
сегменте данных область для обработки файлов. Для получения досту-
па к файлам мы используем метод описателя файла и, так как мы хо-
тим работать более чем с одним файлом, мы пишем программу, присва-
ивая каждому блоку уникальное имя.
file_head MACRO fnum
file_hand_&fnum dw ? ;заголовок файла
file_nmax_&fnum db 49 ;макс.длина имени файла
file_nlen_&fnum db ? ;действит.длина имени файла
file_name_&fnum db 50 dup (?) ;буфер имени файла
ENDM
Почему для fnum (номер файла) мы не использовали директиву
LOCAL? Потому, что для самого макро эти метки не являются локаль-
ными. К ним должен осуществляться доступ из других частей прог-
раммы с целью установки имени файла, получения возможности опери-
рования с блоком управления файлом и т.д. Это макро может быть
усовершенствовано. Что необходимо сообщить программе пофайлового
копирования, если мы хотим одновременно обрабатывать два файла?
Нам следует дважды вызвать file_head (описатель файла):
file_head 1 ;1-ый блок файла
file_head 2 ;2-ой блок файла
Вместо этого, используя директиву REPT (повторить), мы можем
написать file_head так, чтобы он определял столько блоков, сколь-
ко необходимо. Такой макрос приведен в Листинге 1-3.
Листинг 1-3. Описание блока доступа к файлу
------------------------------------------------------------
fcnt = 0 ;определить и иниц-ать символ
file_head2 MACRO fnum
file_hand_&fnum dw ? ;заголовок файла
file_nmax_&fnum db 49 ;макс.длина имени файла
file_nlen_&fnum db ? ;действ.длина имени файла
file_name_&fnum db 50 dup (?) ;буфер имени файла
ENDM
file_head MACRO fnum
REPT fnum ;повторить блок "fnum" раз
file_head2 %fcnt ;создать блок #"fcnt"
fcnt = fcnt + 1
ENDM ;закончить блок повторения
ENDM ;закончить макро file_head
-------------------------------------------------------------
Как показано в Листинге 1-4, при вызове макро file_head оно, в
свою очередь, дважды вызывает макро file_head2, используя каждый
раз новое значение fnum. Конечно, это макрорасширение со значени-
ем статуса листинга, установленного по умолчанию, не показывает
явно обращения к file_head2. Однако результат работы REPT мы мо-
жем видеть по двум созданным блокам управления файлами. Заметим,
- 1-11 -
что директива REPT должна заканчиваться строкой ENDM, равно как и
директива MACRO. Вcе блоки повторения должны заканчиваться ENDM
(конец макро). Аналогично ENDM должно появляться в конце каждого
макроопределения.
Листинг 1-4. Макрорасширение блока описания доступа к файлу
-----------------------------------------------------------------
file_head 2
3 file_hand_0 dw ? ;заголовок файла
3 file_nmax_0 db 49 ;макс. длина имени файла
3 file_nlen_0 db ? ;действит.длина имени файла
3 file_name_0 db 50 dup (?) ;буфер имени файла
3 file_hand_1 dw ? ;заголовок файла
3 file_nmax_1 db 49 ;макс. длина имени файла
3 file_nlen_1 db ? ;действит.длина имени файла
3 file_name_1 db 50 dup (?) ;буфер имени файла
------------------------------------------------------------------
В добавление к директиве REPT мы также использовали счетчик.
Счетчик - это переменная, имеющая цифровое значение. Так как его
значение может меняться, он должен быть определен при помощи опе-
ратора присваивания (=). (В MASM для определения переменных, име-
ющих статические значения, используется оператор equ (прирав-
нять), в то время как для определения переменных, чьи значения
могут изменяться, применяется знак равенства (=).) Счетчик, при-
меняемый в макро file_head, называется fcnt (счетчик файла).
Счетчик fcnt увеличивается на 1 при каждом проходе file_head. Но
почему метки находятся в file_head2, file_hand_0 и т.д., а не в
file_hand_fcnt? Каким образом имя fcnt заменяется на свое значе-
ние? Ответ заключается в операторе "%",стоящем в вызове
file_head2 перед fcnt. Знак процента предписывает замену символа
на его значение. Так как мы использовали знак процента, нам необ-
ходимы два макро. Если бы мы попытались вычислить и подставить
fcnt в одно маkро:
REPT fnum ;повторить блок "fnum" раз
file_hand_&%fcnt dw ? ;заголовок файла ,
возникла бы ошибка символа:
file_hand_fcnt dw ? ;заголовок файла
Оператор процента (%) работает только в аргументах макровызо-
ва! Кроме того, значение символа должно быть абсолютной (непере-
мещаемой) константой.
Другим важным аспектом наших макро является то, что счетчик
fcnt инициализируется вне макроблока. Это делается потому, что мы
не хотим устанавливать fcnt в 0 всякий раз при вызове file_head
(что может вызвать дублирование меток). Однако, fcnt должен быть
где-то инициализирован, или оператор:
fcnt = fcnt + 1
- 1-12 -
вызовет появление сообщения об ошибке "Символ не определен".
Более подробно о макродирективах повторения - IRP и IRPC
Кроме директивы REPT MASM поддерживает еще две директивы мак-
роповторений. Это - IRP (неограниченное повторение) и IRPC (не-
ограниченное повторение символа). В действительности, ничто не
повторяется бесконечно. Вместо этого повторения происходят до тех
пор, пока в списке аргументов есть хоть один аргумент. В Листинге
1-5 приведен пример макроповторения, названного test_vac и пред-
назначенного для добавления элементов в сегмент данных.
Листинг 1-5. Пример макро повторения IRP и его расширение
------------------------------------------------------------------
test_mac MACRO args ;определить "test_mac"
IRP dummy,<&args>
db dummy ;добавить элемент
ENDM ;закончить "IRP"
ENDM ;закончить "test_mac"
test_mac 'one' <-- 1-ый вызов
2 db 'one' ;добавить элемент
test_mac <'two','three','four'> <-- 2-ой вызов
2 db 'two' ;добавить элемент
2 db 'three' ;добавить элемент
2 db 'four' ;добавить элемент
------------------------------------------------------------------
При каждом проходе блока повторения в качестве значения dummy
используется очередное значение списка аргументов. Используя ди-
рективу IRP, мы можем для выполнения трех действий применить
только один вызов макро. При повторном вызове test_ mac блок IRP
повторяется db раз для каждой из трех строк списка аргументов.
Введем для макросов два специальных символа - угловые скобки
(< и >). Макро test_mac предполагает наличие только одного аргу-
мента, а мы хотим переслать ему список аргументов. Угловые скобки
выполняют эту функцию, преобразуя текст, заключенный в них, в
одиночный литерал. Таким образом, 'two','three','four' интерпре-
тируется как один аргумент, а не три. Однако сами угловые скобки
принимающему макро не пересылаются. Внутри test_mac args имеет
значение 'two','three','four', а не <'two','three','four'>. Вот
почему в директиве IRP были добавлены угловые скобки.
Однако это объяснение не применимо к строкам! Одиночные кавыч-
ки, в которые заключаются строки, не допускаются, а добавление
еще одного уровня приводит к путанице . Если мы используем опера-
тор define byte (определить байт) так:
db 'dummy' ;добавить элемент
MASM разворачивает строку следующим образом:
2 db 'dummy' ;добавить элемент
что может создать нам довольно много значений dummy, но не
- 1-13 -
сделает то, что мы хотим. Мы можем вызвать использование дей-
ствительного аргумента через
db '&dummy' ;добавить элемент
но MASM развернет эту строку в
2 db "one" ;добавить элемент
Это приведет к появлению специальной ошибки "Чтение текста
после конца". Такая же ошибка возникает, если Вы случайно создали
бесконечный рекурсивный вызов макро. В общем случае MASM выберет
всю память для сохранения всех используемых символов. Будьте вни-
мательны! Это сообщение об ошибке выдается до тех пор, пока Вы не
прекратите работу MASM, нажав "Cоntrоl-C".
Резюме по использованию макро
Из того , что мы изучили, видно - макросы используют некоторый
тип "стенографического" программирования. Так, если вы определили
некоторый блок программы, Вы можете включать его многократно че-
рез простой вызов макро. Мы видели, что макросы определяются при
помощи оператора MACRO, который присваивает макро имя и дополни-
тельно может снабжать его аргументами. Макроописание заканчивает-
ся оператором ENDM. После выполнения макроописания вызов макро
осуществляется по его имени, за которым могут следовать некоторые
параметры.
Мы также видели, как MASM, используя директиву LOCAL, может
вырабатывать уникальные метки и как применяются директивы повто-
рения. Наши знания о директивах повторения и их использовании бу-
дут расширены в следующей главе.
Справочное Руководство Программиста для Операционной Системы
MS-DOS фирмы Майкрософт содержит макроописания всех системных вы-
зовов. Кроме того, в нем также содержатся некоторые основные мак-
росы, предназначенные для решения общих задач (например, переме-
щение строки). Это руководство является хорошим пособием по
применению макросов и их структурированию. Ниже приводятся три
таблицы, которые наверняка окажутся Вам полезными. В Табл. 1-1
приводится свод макродиректив, используемых MASM. В Табл. 1-2 пе-
речисляются специальные макрооператоры; а в Табл. 1-3 собраны
макродирективы управления листингом.
Теперь мы прошли полпути в изучении макросов для структуриро-
ванных программ. Для выполнения работы по созданию этих макросов
нам необходимо знать, когда и что ассемблируется в программу. Это
тема следующего раздела.
Условное ассемблирование
При написании программ на Ассемблере очень неплохо иметь воз-
можность включения некоторых программных секций. Используя макро-
команды, было бы также неплохо иметь возможность выбирать различ-
ные направления работы программы в зависимости от аргументов,
передаваемых в макро. MASM обеспечивает эти возможности через ус-
ловное ассемблирование.
- 1-14 -
Когда может потребоваться условное ассемблировние? Предполо-
жим, что Вы пишите большую программу, которая, подобно большинс-
тву таких программ, имеет какие-то ошибки. Для выяснения обстоя-
тельств работы программы Вы решаете поместить в нее некоторые
отладочные операторы. Однако , когда у Вас появится уверенность,
что программа выполняется правильно, Вы захотите изъять эти опе-
раторы, чтобы программа выполнялась без лишних кодов. Но так как
программа, вероятно, содержит еще много ошибок, отладочные опера-
торы придется выполнять снова. Добавление и удаление операторов
утомительно. Для решения этой проблемы может быть использовано
условное ассемблирование. В Листинге 1-6 показано воздействие пе-
реключателя "DEBUG" (отладить) на операторы блока при условном
ассемблировании. Реализован хороший способ редактирования прог-
раммы, а переключатель .SALL использован для подавления некоторой
части макрорасширения @TypeStr. Наш интерес обращен только к
строкам, связанным с условным ассемблированием.
Таблица 1-1. Макродирективы
--------------------------------------------------------
Директива Переменная Описание применения
--------------------------------------------------------
mname MACRO parameter_list МАКРООПИСAHИЕ
Сигнализирует о нача-
ле блока макроописа-
ния; parameter_list
определяет формальные
аргументы, используе-
мые в блоке.
ENDM КОНЕЦ МАКРО
Сигнализирует конец
макроописания или бло-
ка повторения REPT,IRP
или IRPC. Наличие обя-
зательно!
EXITM ВЫХОД ИЗ МАКРО
При достижении выход из
макро. Наиболее часто
используется при ус-
ловном ассемблировании.
LOCAL symbol_list ЛОКАЛЬНЫЙ СИМВОЛ
Определяет для ассембле-
ра символы из списка
symbol_list как уникаль-
ные символы.
Расширяется в ??ххх, где
ххх - шестнадцатиричные
числа.
PURGE macro_list УДАЛИТЬ МАКРООПИСAHИЕ
Уничтожает описание макро
из списка macro_list.
- 1-15 -
--------------------------------------------------------
Директива Переменная Описание применения
--------------------------------------------------------
REPT выражение ПОВТОРИТЬ
Повторяет блок команд,
размещенных между REPT
и ENDM,столько раз,сколь-
ко задано в выражении.
ITP dummy, НЕОГРAHИЧЕННОЕ ПОВТОРЕНИЕ
Повторяет блок команд
между IRP и ENDM для
каждого значения из para-
meter_list,заменяя фор-
мальный параметр (dummy)на
значение параметра из каж-
дого расширения.
IRPC dummy, строка СИМВОЛ НЕОГРAHИЧЕННОГО
ПОВТОРЕНИЯ
Повторяет блок команд
между IRPC и ENDM для
каждого символа строки,
заменяя формальный пара-
метр на символ из каж-
дого расширения.
---------------------------------------------------------
Таблица 1-2. Специальные символы в макрокомандах
---------------------------------------------------------
Символ Описание применения
---------------------------------------------------------
&argument Соединяет формальные аргументы или сим-
волы с текстом. Особенно необходим для
подстановки вместо формальных аргументов
в строках, заключенных в кавычки.
;;comment text Обозначает комментарий. Такой
комментарий никогда не приводится в
макроописании.
!char Указывает, что следующий далее символ
является литералом. Используется для
включения &, % и т.д. в макрорасширения,
когда эти символы могут быть интерпре-
тированы не как специальные.
%symbol Используется для преобразования символа
или выражения в число текущей системы
счисления.
Угловые скобки (< и >) используются для
определения текста, заключенного между
ними, как литерала. Все, что заключено
в такие скобки, может быть передано в
макро как одиночный аргумент.
----------------------------------------------------------
- 1-16 -
Таблица 1-3. Директивы управления листингом в
макрокомандах
----------------------------------------------------------
Директива Описание применения
----------------------------------------------------------
.XALL Приводит исходный и объектный код макрорасши-
рений, исключая исходные строки, которые не
вырабатывают исполнительного кода. Устанав-
ливается по умолчанию.
.LALL Приводит все строки макрорасширения, исключая
комментарии, начинающиеся с удвоенных точкой
с запятой /;;/.
.SALL Исключает код, вырабатываемый макрорасшире-
нием.
.LIST Приводит строки исходного текста программы.
Действие противоположно .XLIST, однако сос-
тояние листинга, определяемое директивами
.XALL, .LALL или .SALL не изменяет.
.XLIST Подавляет любую выдачу. Перекрывает все
другие директивы.
---------------------------------------------------------
Листинг 1-6. Условное ассемблирование операторов DEBUG -
FALSE
----------------------------------------------------------------
;часть А - листинг исходного текста
FALSE EQU 0
TRUE EQU 0FFFFh
DEBUG EQU FALSE
.
.
.
@TypeStr 'hello world!'
IF DEBUG <--- начало условного блока
@TypeStr 'Hi -I made it to this point in the program'
ENDIF <--- конец условного блока
.
.
.
;часть В - листинг MASM
@TypeStr 'hello world!'
1 mov dx,offset ??0000
1 mov ax,09h
1 int 21h ;вызвать функцию MS-DOS
ENDIF
----------------------------------------------------------------
- 1-17 -
Данный пример был ассемблирован со значением переключателя
DEBUG - FALSE (ложь). В результате от условного блока в листинге
MASM после расширения @TypeStr появляется только оператор ENDIF.
Таким образом MASM сообщает, что условный блок присутствовал, но
он не ассемблировался. Когда значение переключателя DEBUG изменя-
ется на TRUE (истина), MASM вырабатывает другую программу, приве-
денную в Листинге 1-7.
Листинг 1-7. Условное ассемблирование операторов DEBUG -
TRUE
------------------------------------------------------------------
;листинг MASM
DEBUG EQU TRUE
.
.
.
@TypeStr "hello world"
1 mov dx,offset ??0001
1 mov ax,09h
2 int 21h ;вызвать функцию MS-DOS
IF DEBUG
@TypeStr 'Hi -I made it to this point in the program'
1 mov dx,offset ??0002
1 mov ah,09h
2 int 21h ;вызвать функцию MS-DOS
ENDIF
----------------------------------------------------------------
Все это время отладочные операторы присутствовали в программе.
MASM также включает в листинг строку, вызывающую ассемблирование
операторов. Если Вы предпочитаете увидеть в файле листинга все
директивы условного ассемблирования вне зависимости от того, име-
ют они значение TRUE или FALSE, используйте директиву .LFCOND
(включить список ложных состояний). Позднее Вы можете подавить
выдачу ложных состояний при помощи директивы .SFCOND (подавить
выдачу ложных состояний). В основном, блок условного ассемблиро-
вания начинается с одной из разновидностей оператора IF (если)
(см. полный список в Табл. 1-4) и заканчивается оператором ENDIF.
Обычно использование переключателей TRUE/FALSE в условном ас-
семблировании возникает при программировании систем (программиро-
вание операционных систем компьютеров). Если для Вашего компьюте-
ра имеется исходный текст операционной системы на языке ассембле-
ра, просмотрите его. Вы наверняка найдете, что условное ассембли-
рование используется в нем весьма интенсивно. Условное ассембли-
рование позволяет проектировщику писать одну операционную
систему, а через использование переключателей - конфигурировать
ее на конкретный набор аппаратных средств. Эти переключатели, по-
добно переключателю DEBUG из нашего примера, позволяют сгенериро-
вать работающую систему для данного типа, количества или конфигу-
рации памяти, пульта, периферийных устройств, драйверов и т.д.
Некоторые выражения, которые при вычислении дают 0 или имеют
значение 0, MASM рассматривает как FALSE. Ненулевые значения
рассматривает как TRUE. Обычно для символа TRUE используется
шестнадцатиричное значение FFFFh. Это разрешает использовать TRUE
в любых битовых операциях. Например, поразрядное выполнение AND
- 1-18 -
(логическое И) над 0001 и 1000 равно 0000, так как хотя оба зна-
чения и истинны, их логическое произведение должно быть ложно.
Вспомните, что MASM использует одни и те же операторы как для ло-
гических, так и для битовых операций.
Таблица 1-4. Директивы условного ассемблирования
---------------------------------------------------------
Директива Переменная Описание применения
---------------------------------------------------------
IF выражение IF TRUE (если истина)
Если значение выражения не
нулевое, операторы условно-
го блока ассемблируются.
IFE выражение IF FALSE (если ложь)
Если значение выражения рав-
но нулю, операторы условного
блока ассемблируются.
ELSE ELSE (иначе)
Если значение условной ди-
рективы ассемблирования рав-
но FALSE (ложь) (условный
блок не ассемблируется),
ассемблируются альтернатив-
ные операторы блока ELSE.
Завершает блок IFXXXX, хотя
после должно следовать ENDIF.
Действительно только после
оператора IFXXXX.
ENDIF END блока IF (конец блока IF)
Завершает блок IFхххх или
ELSE.
IF1,IF2 IF MASM проход 1, IF MASM
проход 2
Ассемблирует условный блок,
если MASM-ассемблер осущест-
вляет указанный проход.
См.взаимозависимость между IF1
и IF2 и IFDEF и IFNDEF.
IFDEF символ IF cимвол DEFINED (если сим-
вол определен)
IFNDEF символ IF символ NОT DEFINED (если
символ не определен)
Выясняет, определен ли сим-
вол или он имеет внешнее
объявление. IFNDEF противо-
положно IFDEF. См. взаимо-
связь с проходами ассемблера.
IFB <аргумент> IF аргумент BLANK (если ар-
гумент пуст).
- 1-19 -
---------------------------------------------------------
Директива Переменная Описание применения
---------------------------------------------------------
IFNB <аргумент> IF аргумент NOT BLANK (если
аргумент не пуст).
Выясняет, пуст ли аргумент.
Используется для определения
передаваемых параметров мак-
ро. IFNB противоположноIFB.
Наличие угловых скобок обя-
зательно.
IFIDN , IF строка1 IDENTICAL TO
строка2 (если строка1 иден-
тична строке2)
IFDIF , IF строка1 DIFFERENT FROM
строка2 (если строка1 отлич-
на от строки2)
Определяет, идентичны ли
строка1 и строка2. IFDIF
противоположно IFIDN.
Наличие угловых скобок обя-
зательно.
---------------------------------------------------------
Операторы отношений
Кроме использования символов с предопределенными значениями и
арифметических выражений MASM поддерживает операторы отношений,
которые могут применяться для управления операторами условного
ассемблирования. Операторами отношений являются операторы, выра-
жающие взаимосвязь между двумя значениями. Less than (меньше
чем), greater than (больше чем), equal to (равно) и not equal to
(не равно) - примеры операторов отношений.
Эти операторы позволяют выполнять специальные действия по про-
верке на попадание в диапазон. Использование операторов отношений
разрешает создавать довольно сложные программные структуры, кото-
рые автоматически настраиваются на конкретную среду функциониро-
вания (например, определение размера области данных для запомина-
ния резервной области памяти). Однако, используя операторы
отношений, MASM не всегда выполняет то, что планировалось.
Работая с целыми со знаком, вы можете посчитать OFFFFh и -1 за
одно и то же значение. За некоторым исключением MASM также ис-
пользует взаимозаменяемость значений. Хотя ранние версии MASM при
работе с отрицательными числами имели некоторые сложности, более
новые версии (1.2 и выше) знают, что -1 равно 0FFFFh, однако при
сравнении величин двух чисел MASM рассматривает их по-разному.
Это иллюстрирует следующий пример:
True FFFF dw 1 gt -1 очевидно
False 0000 dw 1 gt 0FFFFh 65535, а не -1
True FFFF dw -1 ge 0FFFFh -1=-1
False 0000 dw -1 gt 0FFFFh -1 не больше -1
В примере показано, что MASM рассматривает 0FFFFh как положи-
тельное целое число 65535, однако при сравнении с -1 0FFFFh ин-
терпретируется как -1. Об этом можно заметить: "Кто предостере-
жен, тот вооружен".
Полный список операторов отношений приведен в Табл. 1-5. При-
- 1-20 -
мер использования этих операторов при программировании макрост-
руктур помещен в конце данной главы. В Табл. 1-6 приведен список
директив условного ассемблирования.
Таблица 1-5. Логические операторы и операторы отношений
условного ассемблирования
------------------------------------------------------------
Оператор Синтаксис Описание применения
------------------------------------------------------------
EQ exp1 EQ exp2 TRUE,если выражение1 равно вы-
ражению2
NE exp1 NE exp2 TRUE,если выражение1 не равно
выражению2
LT exp1 LT ехр2 TRUE,если выражение1 меньше
выражения2
LE exp1 LE exp2 TRUE,если выражение1 меньше
или равно выражению 2
GT exp1 GT exp2 TRUE,если выражение1 больше
выражения2
GE exp1 GE exp2 TRUE,если выражение1 больше
или равно выражению2
NOT NOT exp TRUE,если выражение - FALSE,
иначе FALSE
AND exp1 AND exp2 TRUE,если только оба выражение1
и выражение2 - TRUE
OR exp1 OR exp2 TRUE,если выражение1 либо выра-
жение2 - TRUE
XOR exp1 XOR exp2 TRUE,если выражение1 равно лог.
NOT от выражения2
FALSE (0000 16-CC) Для IF TRUE любое нулевое выра-
жение - FALSE
TRUE (FFFF 16-CC) Для IF TRUE любoe ненулевое вы-
ражение - TRUE
---------------------------------------------------------
Таблица 1-6. Список директив условного ассемблирования
---------------------------------------------------------
Директива Описание применения
---------------------------------------------------------
.LFCOND Приводит список ассемблерных условий, со-
ответствующих FALSE.
.SFCOND Подавляет выдачу списка условий, соответ-
ствующих FALSE. По умолчанию устанавлива-
ется .SFCOND.
.TFCOND Включает список условного ассемблирования
FALSE аналогично переключателю MASM /X.
Действует независимо от переключателей
.LFCOND и .SFCOND.
.LIST Приводит список исходных строк. Противо-
положно .ХLIST, но не изменяет характерис-
тик листинга условного ассемблирования,
определенных .LFCOND,.SFCOND или .TFCOND.
.XLIST Подавляет любую выдачу. Перекрывает все
предыдущие директивы.
------------------------------------------------------------
- 1-21 -
Условное ассемблирование. Выводы
Кратко ознакомившись с условным ассемблированием, мы увидели,
как можно управлять включением в программу тех или иных операто-
ров. В этом плане мы рассмотрели применение условного ассемблиро-
вания для облегчения процесса включения дополнительных частей
программы.
Но мы затронули только часть проблемы. В наших примерах была
разобрана только одна из десяти возможных форм применения услов-
ных операторов. Где еще могут применяться эти операторы? Прежде
всего они могут применяться в макросах. К рассмотрению этой темы
мы теперь и переходим.
Условное ассемблирование и МАКРОСЫ
Хотя условное ассемблирование часто используется с определен-
ными явно ключами, основной потенциал условного ассемблирования
реализуется при его сочетании с возможностью макрокаманд. Сущест-
вует целый набор возможностей условного ассемблирования, которые
специально ориентированы на работу с макрокомандами. Рассмотрим
основные из этих возможностей.
Макросы могут быть разделены на две группы. В первую группу
входят макросы, ориентированные на создание определенных струк-
тур, зависящих от некоторых входных данных, причем эти структуры
хорошо определены, а входные данные принадлежат конкретному клас-
су. Примером может служить макро file_head, предназначенное для
ввода блока определения файла.
Вторая группа макро предназначена для генерации структур, за-
висящих от информации, доступной программисту, или такой информа-
ции, которую программист считает несущественной и подлежащей иг-
норированию. Эти макросы часто должны уметь обрабатывать
множество классов аргументов и определять класс аргументов. В то
же время эти макросы могут поддерживать локальные данные или
счетчики, освобождая программиста от рутинной работы. Макросы
структурного управления, приводимые в оставшейся части данной
главы, являются первыми примерами второй группы. Конечно, макросы
этих двух групп обычно частично перекрываются.
Для одного типа макро программист использует эти средства для
того, чтобы избежать дополнительного ввода данных с клавиатуры
или какой-либо другой неблагодарной работы. Для другого типа
программист использует эти средства для создания структур высоко-
го уровня, основанных на способности ассемблера поддерживать про-
пущенную информацию. В целях упрощения своей работы программист
умышленно прячет детали реализации.
Примером макро высокого уровня является использование макро,
упрощающих применение мнемоник в ассемблере. Хотя большинство ко-
манд процессора 8086 может использоваться с регистровыми операн-
дами или операндами памяти, многие из них не позволяют непосредс-
твенных операндов. Примером является команда PUSH, хотя 186/188
- 1-22 -
и 286 позволяют проталкивать в стек непосредственные данные.
Довольно просто сконструировать макро pushi (проталкивание не-
посредственное), передающее нужный аргумент в регистр и проталки-
вающее этот регистр. Однако, если макро предназначено для реали-
зации более общей функции проталкивания, желательно, чтобы оно не
только проталкивало непосредственные данные, но и само решало,
является ли такая операция необходимой. Другими словами, програм-
мист хотел бы применять обобщенный pseudoopcode (псевдокод), ра-
ботающий во всех случаях.Реально псевдокод должен быть макро, вы-
числяющим операнды и генерирующим стандартный или расширенный
набор команд.
Первый шаг в написании такого общецелевого макро заключается в
определении операндов. Для решения этой задачи MASM имеет целый
набор специальных операторов.
Определение типов операндов
В среде 8086/8088 существует четыре основных типа операндов.
Это - регистровый операнд, непосредственный операнд, операнд па-
мяти и адреса. Для операндов, ориентированных на данные, возможны
подтипы. Регистры представляют собой специальные случаи суммато-
ров (регистр общего назначения A) и регистры сегментов. Все три
типа данных могут подразделяться на 8- и 16-битовые данные. Адре-
са могут быть near (близкие, состоящие только из смещения) или
far (далекие, состоящие из смещения и сегмента).
Как мы будем различать все эти типы? Мы будем использовать
операторы MASM .TYPE и TYPE. В Табл. 1-7 приведены результаты ис-
пользования этих операторов с различными классами операндов.
Таблица 1-7. Операторы MASM .TYPE и TYPE
Правила для .TYPE и TYPE
---------------------------------------------------------
Оператор Результат
---------------------------------------------------------
.TYPE биты 5 и 7 8х Определено внешне
2х Определено локально
0х Неверная ссылка
.TYPE биты 0 ... 2 х0 Абсолютный режим
х1 Программозависимый
х2 Зависимый по данным
TYPE с переменной 01 Переменная в байт
02 Переменная в слово
04 Переменная в двойное
слово
08 Переменная в четверное
слово
10 Переменная в 10 байтов
ХХ Структура размерностью
в ХХ
TYPE с программной меткой FFFF "Близкая" программная
метка
FFFE "Далекая" программная
метка
-------------------------------------------------------------
- 1-23 -
Примеры для .TYPE и TYPE
-------------------------------------------------------------
Тип переменной .TYPE Определено TYPE Определено
-------------------------------------------------------------
Непосредственный 20 локально 0 неверно
Регистровый 20 локально 0 неверно
Метка данных 22 локально Х количество
байтов
"Близкая" метка 21 локально FFFF "близкая"
метка
"Далекая" метка 21 локально FFFE "далекая"
метка
Код операции MASM 00 неверно 0 неверно
Нонсенс 00 неверно 0 неверно
-------------------------------------------------------------
Список примеров можно продолжить. Хотя .TYPE и распознает име-
на различных регистров, он не распознает регистровые конструкции
типа [BX] или ARRAY[BX][SI]. Односимвольная константа типа A рас-
познается оператором .TYPE как переменная, определенная локально.
Во время первого прохода ассемблера ссылка вперед никак не
распознается. IFDEF в качестве результата возвращает "не опреде-
ленно", .TYPE возвращает "неверно", а TYPE возвращает нулевую
длину. К ссылкам вперед может быть применено только одно правило:
если возможно, не применять их вообще.
Фазовые ошибки и некоторые особенности MASM
С использованием операторов MASM связана одна важная особен-
ность. MASM является двухпроходным ассемблером, назначающим зна-
чения символам на первом проходе и затем вычисляющим символы при
втором проходе. Программные метки и метки данных являются симво-
лами. Их значения определяются во время первого прохода, а затем
используются на втором проходе для генерации программного кода.
Рассмотрим следующее дерево событий. При обнаружении ссылки
вперед MASM не распознает метку на первом проходе и не способен
определить ее тип. Попытка ссылки на этот символ вызывает появле-
ние сообщения об ошибке "Символ не определен". MASM обнаруживает
эту ошибку на первом проходе, но подавляет ее и продолжает ас-
семблирование. MASM способен подавить ошибку, предположив тип
символа из контекста, в котором он появился. Если это предположе-
ние неверно, MASM может закончить свою работу, выдав сообщение
"фазовая ошибка между проходами", или может укоротить команду и
поместить после нее команду nop (нет операции), как заполнитель.
Существует два способа устранения фазовых ошибок при нормаль-
ной работе MASM. В большинстве случаев MASM способен определить
тип операндов из контекста. Программисты редко применяют переходы
в сегменте данных и обычно не добавляют программных адресов. Для
тех специальных случаев, когда MASM делает ошибочные предположе-
ния, программист может определить порядок работы ассемблера, ис-
пользуя оператор перекрытия PTR (указатель). С помощью оператора
- 1-24 -
PTR программист может явно указать тип ссылки вперед, и, следова-
тельно, MASM не сделает ошибочного предположения. Однако, пытаясь
выработать многоцелевые команды макро, мы значительно увеличиваем
вероятность ошибочного выражения. Если наши многоцелевые команды
предназначены для обработки любых классов операндов, точное зна-
чение такого класса трудно определить из контекста. Кроме того,
хотя использование PTR и может помочь в некоторых подобных случа-
ях (как мы увидим в макро @PushOP), оно не достигает цели осво-
бождения программиста от излишних деталей.
Проанализировав то, как ошибочное предположение вырабатывает
фазовую ошибку, мы с большей вероятностью можем устранить ее по-
явление. Так как фазовые ошибки являются результатом смены неко-
торыми символами (например, меток) своих значений между прохода-
ми, важно, чтобы макросы вырабатывали один и тот же объем
программного кода на каждом проходе. Это предохраняет значения
меток, размещенных после макро, и объясняет, почему MASM забивает
укороченные команды инструкциями NOP. От одного прохода к другому
должны также оставаться постоянными программные метки.
Сравнение строк. Пример
К сожалению, способность оператора .TYPE распознавать непос-
редственные операнды и регистры значительно уменьшается при выяс-
нении типа операндов макро. Так как особенно важно знать, являет-
ся ли аргумент макро регистром, мы должны сконструировать способ
выявления этого. Определение того, является ли аргумент регист-
ром, обычно полезно только вместе с неявным предположением, что
если он не является регистром и не является определенной адресной
ссылкой, то предположительно является ссылкой на непосредственные
данные.
Это хорошо сочетается с использованием условного ассемблирова-
ния на базе директив IRP и IRPC. Целью в данном случае является
определение принадлежности аргумента макро к какому-либо набору.
Для решения задачи - является ли аргумент регистром, используется
сопоставление строк. Так как оператор .TYPE может определить
только то, что эти регистры объявлены локально и абсолютны, для
явной проверки имени регистра применяется макро сопоставления
строк. Эту функцию выполняет приведенное в Листинге 1-8 макро
?reg.
Листинг 1-8. Макро сопоставления имен регистров - ?reg
-----------------------------------------------------------------
FALSE EQU 0
TRUE EQU 0FFFFh
;;
;;**** ?REG - Определить, является ли аргумент регистром
;;
?reg MACRO arg
?isr8 = FALSE
?isr16 = FALSE
IRP reg,
IFIDN <&®>,<&arg>
?isr16 = TRUE
EXITM
- 1-25 -
ENDIF
ENDM ;; конец секции IRP
;; Если сравнились, остановиться здесь
IF (?isr16)
EXITM
ENDIF
;; Если еще не сравнились, продолжить далее
IRP reg,
IFIDN <&®>,<&arg>
?isr8 = TRUE
EXITM
ENDIF
ENDM ;; конец секции IRP
;; Если сравнились, остановиться здесь
IF (?isr8)
EXITM
ENDIF
;; Если не сравнились, попробовать прописные имена регистров
IRP reg,
IFIDN <&®>,<&arg>
?sir16 = TRUE
EXITM
ENDIF
ENDM ;; конец секции IRP
;; Если сравнились, остановиться здесь
IF (?isr16)
EXITM
ENDIF
;; Если еще не сравнились, попробовать еще
IRP reg,
IFIDN <&®>,<&arg>
?isr8 = TRUE
EXITM
ENDIF
ENDM ;; конец секции IRP
ENDM ;; конец макроописания
-----------------------------------------------------------------
Cердцевиной этого макро, как и любого макро сопоставления, яв-
ляются команды:
IRP reg,
IFIDN <&®>,<&arg1>
?isr16 % TRUE
Интерпретировать эти строки можно так:
Для reg равного ax...ss выполнить . . .
Если reg равно аргументу arg . . .
Аргумент есть регистр!
Здесь следует остановиться на двух интересных моментах.
Во-первых, необходимо явно проверять имена регистров, написанные
как малыми, так и большими буквами. Директива условного ассембли-
рования IFIDN сравнивает строки на точное соответствие. Несмотря
на все усилия, макро ?reg не полно. Оно не сопоставляет имена ре-
гистров, состоящие из одной большой и одной малой буквы (например
- 1-26 -
"aL"). Во-вторых, необходимо проводить две проверки: одну для
16-битовых регистров и одну для 8-битовых регистров. В данной ре-
ализации наличие двух отдельных проверок не приносит нам никакой
выгоды, однако такие проверки окажутся полезными в следующем при-
мере.
Макро ?reg имеет два дополнительных синтаксических элемента.
Один - директива завершения макро EXITM. Эта директива использу-
ется для завершения работы макро ?reg при обнаружении совпадения.
Менее очевидно использование двойного амперсанда в операторе
IFIDN. Согласно Руководству по MASM фирмы Майкрософт пользователь
должен "указывать столько амперсандов, сколько имеется уровней
вложенности". Столь лаконичное выражение не вносит ясности в ре-
шение проблемы. "Уровни вложенности" относятся не к глубине бло-
ков, где появляется ссылка, а к глубине блоков, где находится ее
описание. Таким образом, arg1 приводится только с одним амперсан-
дом, в то время как reg, описание которого находится во вложенном
блоке, требует наличия двух амперсандов. Фирма Майкрософт не ут-
верждает, что это предел разрешенного количества уровней вложен-
ности или количества требуемых амперсандов. В тех случаях, когда
казалось бы необходимо указывать множество амперсандов, попытки
написания примеров, позволяющих выявить правильное функционирова-
ние, не увенчались успехом.
Приведенный Листинг 1-9 с макро ?reg показывает, что это макро
выполняет возложенную на него функцию. Заметьте,что регистр bР,
который распознается MASM, отбрасывается макро ?reg. Это может
быть истолковано, как необходимость соблюдения строгого правила
ввода исходного текста программы с клавиатуры.
Листинг 1-9. Тест макро сравнения имен регистров - ?reg
-----------------------------------------------------------------
?reg ax ; "AX" - регистр ?
FFFF dw ?isr16 <--- TRUE
?reg CS ; "CS" - регистр ?
FFFF dw ?isr16 <--- TRUE
?reg zork ; "ZORK" - регистр ?
0000 dw ?isr16 <--- FALSE
0000 dw ?isr8 <--- FALSE
?reg 01234h ; "1234" - регистр ?
0000 dw ?isr16 <--- FALSE
0000 dw ?isr8 <--- FALSE
?reg bР ; "BP" - регистр ?
0000 dw ?isr16 <--- FALSE
0000 dw ?isr8 <--- FALSE
-----------------------------------------------------------------
Синтаксический анализ аргументов макро
С помощью макро, распознающего имена регистров, можно реализо-
вать обобщенное макро PUSH, которое мы назовем @PUSHOP (протолк-
нуть операнд). (Замечание: мы рассматриваем имя pusha для " про-
толкнуть все", но PUSHA является кодом операции для чипов 186,188
и 286 фирмы Intel. Использование его для макро может ограничить
совместимость снизу вверх. Конечно, Вы всегда можете использовать
команду PUSHA в макро pusha для микропроцессоров 8086 и 8088).
Как упоминалось ранее относительно типа операнда, который не
- 1-27 -
определен и не является регистром, необходимо делать некоторые
предположения. Для макро @PUSHOP предположим, что неизвестные
операнды являются ссылками на непосредственные данные. @PUSHOP
ссылается на макро ?reg, и макро ?reg должно быть включено в
программу с @PUSHOР. Макро @PUSHOP см. в Листинге 1-10.
@PUSHOP использует возможность макро ?reg различать 16- и 8-
битовые регистры. Так как команда PUSH не обрабатывает 8-битовый
регистр, для получения первого символа имени регистра использует-
ся директива IRPC. Затем @PUSHOP добавляет х, формируя ,таким об-
разом, имя 16-битового регистра, которое приемлемо для PUSH. За-
метим, что в этом операторе снова необходимо использование
удвоенного амперсанда, причем с обеих сторон формального аргумен-
та, так как сцепление строк возникает с обоих концов.
Для тех случаев, когда предполагается наличие непосредственных
данных, вызывается макро @PushIm. Это макро более сложное , чем
минимально необходимое, так как предполагается, что для передачи
непосредственных данных в стек нельзя использовать регистры.
Вместо этого макро использует указатель базы (BP) на адрес стека.
После сохранения BP и AX в стеке @PushIm заносит непосредственные
данные поверх содержимого AX, обменивая их со старым содержимым
BP. После восстановления содержимого BP макро извлекает содержи-
мое AX, выталкивая его из стека. Макро @PushIm приведено в Лис-
тинге 1-11.
Листинг 1-10. Обобщение макро PUSH - @PushOp
----------------------------------------------------------------
;; **** @PushOp макро с обобщенным операндом команды PUSH
;; Если операнд определен, он может быть:
;; регистром
;; ссылкой на данные
;;
;; Если операнд не определен, он полагается ссылкой на
;; непосредственные данные
@PushOp Macro arg
.SALL
IFDEF &arg ;; операнд определен ...
?argtyp = .type &arg ;; выявить его тип
IF ((?argtyp and 3) EQ 2) ;;операнд - данные
?argsiz = ((type &arg) + 1)/2 ;; получить длину
;; в словах
?argoff = 0 ;; установить смещение в 0
REPT ?argsiz ;; повторить для каждого слова
?argadd = word ptr &arg + ?argoff ;;получить
.XALL тип ptr
push ?argadd ;;продв-ть непоср.в память
.SALL
?argoff = ?argoff + 2 ;;след-ее слово данных
ENDM
ENDIF
IF ((?argtyp AND 3) EQ 1) ;;операнд - программа
@PushImOff &arg ;;продвинуть смещение метки
ENDIF
IFE (?argtyp and 3) ;;операнд - абс.значение
?reg &arg
IF (?isr16) ;;операнд - регистр 16
.XALL
- 1-28 -
push &arg ;;продвинуть непосред.
.SALL
ELSE
IF (?isr8) ;;операнд - регистр 8
IRPC chr1,&arg1
.XALL
push &&chr1&&x ;;сохранить короткий рег.
.SALL
EXITM
ENDM
е ELSE ;;предположить непосред.данны
@PushIm &arg ;;продвинуть непосред. данные
ENDIF
ENDIF
ENDIF
ELSE ;;продвинуть непосред.данные
@PushIm &arg
ENDIF
ENDM ;;конец макроописания
-----------------------------------------------------------------
Листинг 1-11. Макро проталкивания непосредственных данных -
@PushIm
----------------------------------------------------------------
;; **** @PushIm макро проталкивания непосредственных данных
@PushIm Macro arg
.XALL
push bp ;;сохранить указатель базы
mov bp,sp ;;переместить указатель стека в BP
push ax ;;сохранить накопитель
mov ax,&arg ;;получить непосредственные данные
xchg [bp],ax ;;обменять старый BP и непосред. данные
mov bp,ax ;;восстановить старый BP из AX
pop ax ;;восстановить накопитель
.SALL
ENDM ;;конец макроописания
----------------------------------------------------------------
Эта операция свертывания может быть сведена к обмену элемента-
ми внутри стека. Однако ведение подобных игр со стеком опасно.
Если Ваш компьютер поддерживает прерывания, для предохранения це-
лостности данных стека эта операция должна выполняться только че-
рез запрещение прерываний.
Для тех случаев, когда делается попытка протолкнуть в стек
программные адреса, мы предполагаем, что программист желает сох-
ранить действительное смещение метки. Для проталкивания в стек
смещения метки, как непосредственных данных, было использовано
макро @PushImOff. Оно отличается от макро @PushIm только исполь-
зованием команды:
mov ax,offset &arg ,
что противоположно простому перемещению в макро @PushIm. Макро
@PushImOff приведено в Листинге 1-12.
- 1-29 -
Листинг 1-12. Макро продвижения в стек смещения
непосредственных данных - @PushImOff
---------------------------------------------------------------
;; **** @PushImOff макро продвижения смещения непосред.данных
@PushImOff MACRO arg
.XALL
рush bp ;;сохранить указатель базы
mov bp,sp ;;переместить указатель стека в BP
push ax ;;сохранить накопитель
mov ax,offset &arg ;;получить смещение
;;непосред.данных
xchg [bp],ax ;;обменять старый BP и непоср.данные
mov bp,ax ;;восстановить старый BP из AX
pop ax ;;восстановить накопитель
.SALL
ENDM ;;конец макроописания
----------------------------------------------------------------
Последним дискретным случаем, который распознает @PushOp, яв-
ляется попытка прямого проталкивания в стек данных памяти. Слож-
ность в этом случае заключается в том, что стек воспринимает
только 16-битовые данные. Используя директиву перекрытия PTR,
можно убедить MASM сохранять нужные данные пословно. @PushOp со-
держит цикл, который повторяет эту операцию для каждого слова
сохраняемых данных, увеличивая адрес на два при каждом проходе
цикла. Таким образом можно сохранять в стеке двойные слова, чет-
верные слова, 10-байтовые данные и структуры данных.
Наконец, заметим, что макро @PushOp все еще не обрабатывает
ссылки, содержащие сложную адресацию (2[BP] и т.д.). В случае не-
обходимости Вы можете применить подобные проверки, используя мак-
родирективу IRPC для выявления в аргументах квадратных скобок,
адресации "база + индекс" и "база + смещение".
Итоговый тест макро @PushOp приведен в Листинге 1-13, который
содержит результат нескольких вызовов макро @PushOp.
Последняя операция листинга, где @PushOр обрабатывает 4-слов-
ную переменную, не может быть изъята. Каждое проталкивание имеет
один и тот же аргумент. Однако из этого красивого листинга не
видно, что каждая его строка имеет перемещаемый адрес (0000 для
первого слова, 0002 для второго и т.д.). К сожалению, мы не можем
в данной книге привести 132-колоночные листинги, и, если Вы хоти-
те проверить, попробуйте получить такой листинг сами.
Этот пример особенно полезен тем, что демонстрирует одну об-
ласть, где применение макро почти всегда предпочтительнее приме-
нения подпрограмм. Что касается обработки стека (как в @PushIm и
@PushImOff), макросы способны выполнять эти операции без "угро-
зы" влияния команды CALL на стек. Это особенно важно при переме-
щении или изъятии данных из стека, так как подпрограмма не может
изменить вершину стека и осуществить возврат, не вызвав серьезных
осложнений.
Некоторые предупреждения
по использованию условного ассемблирования
и макросов в MASM
Применяя макросы, мы старались забыть, что они вырабатывают
- 1-30 -
построчный код и не вызывают программ. Хотя это и представляет
некоторые преимущества при быстрой генерации программного кода и
освобождает нас от ограничений по использованию стека, результа-
том линейной программы является более объемный код. Как програм-
мист, Вы должны решить, когда целесообразно вызывать быстро рабо-
тающее макро, а когда - подпрограмму, экономящую память, но
имеющую разветвленную структуру. В общем случае используйте мак-
ро, когда программный код невелик, а время критично, или когда Вы
хотите конфигурировать программу, настроив ее на конкретные усло-
вия. Используйте подпрограмму, когда программный код велик и его
нужно расположить в одном месте (так, чтобы его можно было легко
изменить).
Другим тонким моментом работы макро является использование
символов. Вы помните, что символы определяются при помощи опера-
тора equ или =. Затем эти символы вычисляются MASM и заменяются
их значениями. Иногда случается, что программист забывает, что
аргументы макро не являются символами и наоборот. Согласно Руко-
водству по MASM аргументы макро заменяются действительными пара-
метрами с использованием подстановки "один к одному". Аргументы
макро могут создаваться в одном макро и, используя возможность
текстовой подстановки, передаваться как составная строка текста в
другое макро. Это невозможно с символами. Символам может быть
присвоено текстовое значение при помощи оператора equ, что не
позволяет модифицировать их впоследствии. Только оператор = раз-
решает присваивать символам цифровые значения или атрибуты TYPE.
Пример такого ограничения и один из способов его обхода представ-
лен при рассмотрении операторов структурного управления.
Листинг 1-13. Пример расширения обобщенного макро @PushOp
-----------------------------------------------------------------
dat_seg SEGMENT
datq dq 4040414142424343h
dat_seg ENDS
.
.
.
start:
@PushOp ax ;сохранение общего регистра
1 push ax
@PushOp cs ;сохранение регистра сегмента
1 push cs
@PuchOp al ;сохранение короткого регистра
2 push ax... ;сделать общий регистр
@PushOp 01234h ;сохранить константу
2 push bp
2 mov bp,sp
2 push ax
2 mov ax,01234h
2 xchg [bp],ax
2 mov bp,ax
2 pop ax
@PushOp 'A' ;сохранение константы
2 push bp
2 mov bp,sp
2 push ax
- 1-31 -
2 mov ax,'A'
2 xchg [bp],ax
2 mov bp,ax
2 pop ax
@PushOp start ;сохранить смещение програм.метки
2 push bp
2 mov bp,sp
2 push ax
2 mov ax,offset start
2 xchg [bp],ax
2 mov bp,ax
2 pop ax
@PushOp datq ;сохранить четверную переменную
2 push ?argadd ;1-ое слово
2 push ?argadd ;2-ое слово
2 push ?argadd ;3-ое слово
2 push ?argadd ;4-ое слово
.
.
.
----------------------------------------------------------------
Структурные операторы управления в языке Ассемблер
Теперь, когда мы имеем все необходимые средства для построения
структурных операторов управления, разрешите приступить к этому.
Наиболее часто употребимые операторы структурного управления
представлены в Табл. 1-8.
Операторы Табл. 1-8 наиболее часто используются для реализации
структурного управления в структурированных программах. В некото-
рых языках их больше; в других - меньше. Заметим только, что
ФОРТРAH добился использования структуры IF-THEN-ELSE в ФОРТРА-
НЕ-77. Почти все ассемблеры не имеют таких структур для целей ко-
дирования, хотя многие из них поддерживают IF-THEN-ELSE для ус-
ловного ассемблирования. Причина проста: полагается, что
ассемблеры находятся на более низком уровне чем языки высокого
уровня. Так как мы решили, что эти структуры могут значительно
упростить программирование, и мы можем реализовать их, используя
средства, рассмотренные ранее.
Мы упустили из рассмотрения одну конструкцию. Это - оператор
CASE (выбор). Конструкция, которую мы представим, заимствована из
синтаксиса языка Паскаль, однако аналогичные конструкции имеются
в Си и других языках программирования. Задача оператора CASE зак-
лючается в проверке на равенство значения ключевой переменной var
элементу из списка. Если исходный оператор и варианты из списка
не содержатся в одном и том же макро, невозможно определить, что
является ключевой переменной. Вспомним, что MASM не разрешает ис-
пользовать строки с (=) оператором назначения символа.
Создать оператор CASE можно, перечисляя для него все вероятные
случаи и соответствующие им метки в качестве аргументов одного
макро. Это псевдомакро рассматривается в следующем разделе данной
главы.
Полный список описаний структурированных макросов управления
приведен в Листинге 1-14. Обратите внимание на обильное использо-
вание комментариев макро (;;), экономящих память области макро.
Эти макросы вырабатывают множество символов.Они могут использова-
ться в любом допустимом порядке с теоретическим ограничением уро-
- 1-32 -
вней вложенности до 89. Однако MASM скорее выходит за пределы па-
мяти, чем за предел вложенности. Никакая инициализация не требу-
ется. Все символы самоинициализирующиеся.
Таблица 1-8. Операторы структурного управления
--------------------------------------------------------
Оператор Структура
--------------------------------------------------------
IF-THEN IF<условие> (выполнить, если условие
истинно)
ENDIF
IF-THEN-ELSE IF<условие> (выполнить, если условие ис-
тинно)
ELSE (выполнить, если условие ложно)
ENDIF
DO-WHILE WHILE <условие> (выполнить, если усло-
вие истинно)
END_WHILE
REPEAT-UNTIL REPEAT (выполнить, если условие ложно)
UNTIL <условие>
FOR-DO FOR = to (выполнить
для каждого целого значения var между
begin и end включительно, увеличивая
или уменьшая var при каждом прохождении
цикла)
END_FOR
CASE_OF_ CASE OF
(выполнить, если var = A)
(выполнить, если var = B)
. . .
(выполнить, если var = N)
<по умолчанию> (выполнить, если нет со-
ответствия)
END_CASE
Листинг 1-14. Структурированные макросы управления
-----------------------------------------------------------------
PAGE 50,132 ;установить выдачу листинга на полный экран
;;********************************************************
;; М А К Р О О П И С А Н И Я
;;********************************************************
;;
FALSE EQU 0 ;определить "FALSE"
TRUE EQU 0FFFFh ;определить "TRUE"
;;
;;** @TestSym *************************SUPPORT MACRO******
;; Выявить, был ли определен уровень вложенности. Если нет,
;; то установить "?SYMDEF" для инициализации счетчика этого
;; уровня. Обычно все процессы первого прохода запускают
;; счетчик с 0. Значения всех символов в начале прохода 2
;; должны быть сброшены. Заметьте, что символы "?p2sw..."
;; остаются для "Phase 2 SWitch" (переключатели фазы 2).
;; Проверьте, что первым уровнем при реинициализации явля-
- 1-33 -
;; ется уровень 10. Замечание: значение 10 выбрано для пер-
;; воначального уровня с тем, чтобы зарезервировать для
;; уровня вложенности две цифры.
;;
@TestSym MACRO p1,p2
IF1 ;;если 1-ый проход,проверить что определено
IFNDEF &p1&p2
?p2sw&p1&p2 = TRUE ;;установить для 2-го прохода
;;переопределение переключателей,
?symdef = FALSE ;;вызывающее инициал-ию счетчика
ELSE
?symdef = true ;;разрешить приращение счетчика
ENDIF ;;конец проверки описания символа
ENDIF ;;конец проверки 1-го прохода
;;
IF2 ;;если 2-ой проход, переинициал-ать
IF (?p2sw&p1&p2) ;;если переинициал. нет, то
?p2sw&p1&p2 = FALSE ;;очистить переключатель переопре-
;;деления 2-го прохода
IF (?p2sw&р1&10) ;; и проверить инициал-ию
;;уровня 10
.ERR ;; выйти с сообщением об ошибке
%OUT @TestSym mfcro: &p1 уровень вложенности не
%OUT закрыт на 2-ом проходе
ENDIF ;;закон. уровень 10 по проверке инициал-ции
?symdef = FALSE ;;вызвать переинициал-ию счетчика
ELSE
?symdef = TRUE ;;разрешить приращение счетчика
ENDIF ;;закончить проверку "если не переинициал."
ENDIF ;;закончить проверку прохода 2
ENDM ;;закончить макроописание
;;
;;**@ZeroSym**************************SUPPORT MACRO********
;; Инициализировать счетчик последовательности вложенностей
;; при первом использовании
@ZeroSym MACRO p1,p2
&p1&p2 = 0
ENDM
;;
;; ** @IncSym *********************** SUPPORT MACRO ******
;; Увеличить на 1 счетчик последовательности вложенностей
@IncSym MACRO p1,p2
&p1&p2 = &p1&p2 + 1
ENDM
;;
;; ** @DecSym *********************** SUPPORT MACRO *****
;; Уменьшить на 1 счетчик последовательности вложенностей
@DecSym MACRO p1,p2
&p1&p2 = &p1&p2 -1
ENDM
;;
;; ** @MakeJmp2 ********************** SUPPORT MACRO *****
;; Вставить команду перехода (JMP) в программный код
@MakeJmp2 MACRO p1,p2,p3
jmp &p1&p2&p3
ENDM
- 1-34 -
;;
;; ** @MakeJmp *********************** SUPPORT MACRO *****
;; Переформатировать символы для команды JMP
@MakeJmp MACRO p1,p2,p3
??tmp = &p3&p2
@MakeJmp2 p1,p2,%??tmp
ENDM
;;
;; ** @MakeJmpLabel2 *****************SUPPORT MMACRO *****
;; Вставить метку команды JMP в программный код
@MakeJmpLabel2 MACRO p1,p2,p3
&p1&p2&p3:
ENDM
;;
;; ** @MakeJmpLabel ****************** SUPPORT MACRO *****
;; Переформатировать символы для определения метки опера-
;; тора JMP
@MakeJmpLabel MACRO p1,p2,p3
??tmp = &p3&p2
@MakeJmpLabel2 p1,pp2,%??tmp
ENDM
;;
;; ** @IfTrue ************ STRUCTURED CONTROL MACRO *****
;; Структурированное макро "IF" - IF TRUE
@IfTrue MACRO p1
LOCAL iftrue
j&p1 iftrue ;;перейти к секции "IF"
IFNDEF ?if_level ;;установить новый уровень
;;вложенности
?if_level = 10
ELSE
?if_level = ?if_level + 1
ENDIF
@TestSym ?if_nest,%?if_level ;;установить новый
;;номер последовательности
If (?symdef)
@IncSym ?if_nest,%?if_level
ELSE
@ZeroSym ?if_nest,%?if_level
ENDIF
;; Вставить переход в секцию "ELSE" или "IF NOT"
@MakeJmp ?if_,%?if_level,?if_nest
iftrue:
ENDM
;;
;; ** @IfElse *********** STRUCTURED CONTROL MACRO *****
;; Структурированное макро "ELSE"
@IfElse MACRO
IFNDEF ?if_level
; ОШИБКА - "@IfElse" без открытого оператора "@IfTrue"
EXITM
ENDIF
IF(?if_level LT 10)
; ОШИБКА - "@IfElse" без открытого оператора "@IfTrue"
EXITM
ENDIF
;; Сгенерировать код "@IfElse"
@IncxSym ?if_nest,%?if_level
- 1-35 -
@MakeJmp ?if_,%?if_level,?if_nest
@DecSym ?if_nest,%?if_level
@MakeJmpLabel ?if_,%?if_level,?if_nest
@IncSym ?if_nest,%?if_level
ENDM
;;
;; ** @IfEnd *********** STRUCTURED CONTROL MACRO *****
;; Структурированное макро "END" для совместного
;; использования с "@IfTrue"
@IfEnd MACRO
IFNDEF ?if_level
; ОШИБКА - "@IfEnd" без открытого оператора "@IfTrue"
EXITM
ENDIF
IF (?if_level LT 10)
; ОШИБКА - "@IfEnd" без открытого оператора "@IfTrue"
EXITM
ENDIF
;; Сгенерировать метку "@IfEnd"
@MakeJmpLabel ?if_,%?if_level,?if_nest
?if_level = ?if_level - 1
ENDM
;;
;; ** @DoWhile ******** STRUCTURED CONTROL MACRO *****
;; Структурированное макро "DO_WHILE"
@DoWhile MACRO p1,p2,p3
LOCAL iftrue
IFNDEF ?do_level ;;установить новый уровень
;;вложенности
?do_level = 10
ELSE
?do_level = ?do_level + 1
ENDIF
;; Установить номер новой последовательности для уровня
;; вложенности
@TestSym ?do_nest,%?do_level
IF (?symdef)
@IncSym ?do_nest,%?do_level
ELSE
@ZeroSym ?do_nest,%?do_level
ENDIF
;; Вставить метку начала цикла
@MаkeJmpLabel ?do_,%?do_level,?do_nest
;; Вставить в программный код проверку условия
cmp &p1,&p3
;; Перейти к секции "DO_WHILE_TRUE"
j&p2 iftrue
;; Перейти к следующей метке последовательности
@IncSym ?do_nest,%?do_level
;; Вставить в программный код переход на конец цикла
@MakeJmp ?do_,%?do_level,?do_nest
;; Начать секцию "DO_WHILE_TRUE"
iftrue:
ENDM
;;
;; ** @DoExit *********** STRUCTURED CONTROL MACRO *****
;; Структурированное макро "DO_EXIT" для совместного
- 1-36 -
;; использования с "@DoWhile"
@DoExit MACRO
;; Вставить в программный код переход на конец цикла
@MakeJmp ?do_,%?do_level,?do_nest
ENDM
;;
;; ** @DoEnd ************ STRUCTURED CONTROL MACRO *****
;; Структурированное макро "DO_END" для совместного
;; использования с "@DoWhile"
;; Макро @DoEnd генерирует программный код для структури-
;; рованного ENDDO
@DoEnd MACRO
IFNDEF ?do_level
; ОШИБКА - "@DoEnd" без открытого оператора "@DoWhile"
EXITM
ENDIF
IF (?do_level LT 10)
; ОШИБКА - "@DoEnd" без открытого оператора "@DoWhile"
EXITM
ENDIF
;; Переход на предыдущую метку последовательности
@DecSym ?do_nest,%?do_level
;; Сгенерировать переход на начало цикла
@MakeJmp ?do_,%?do_level,?do_nest
;; Перейти к следующей метке последовательности
@IncSym ?do_nest,%?do_level
;; Сгенерировать метку для "@DoEnd"
@MakeJmpLabel ?do_,%?do_level,?do_nest
?do_level = ?do_level - 1
ENDM
;;
;; ** @Repeat *********** STRUCTURED CONTROL MACRO *****
;; Структурированное макро "@Repeat"
;; "@Repeat" генерирует программный код для структури-
;; рованного REPEAT-UNTIL
@Repeat MACRO
IFNDEF ?rep_level ;;установить новый уровень
;;вложенности
?rep_level = 10
ELSE
?rep_level = ?rep_level + 1
ENDIF
;; Установить новый номер последовательности для уровня
;; вложенности
@TestSym ?rep_nest,%?rep_level
IF (?symdef)
@IncSym ?rep_nest,%?rep_level
ELSE
@ZeroSym ?rep_nest,%?rep_level
ENDIF
;; Вставить метку перехода на начало цикла
@MakeJmpLabel ?rep_,%?rep_level,?rep_nest
ENDM
;;
;; ** @Until ***********STRUCTURED CONTROL MACRO *****
;; Структурированное макро "@Until" для совместного
;; использования с "@Repeat"
- 1-37 -
@Until MACRO p1,p2,p3
LOCAL iftrue
IFNDEF ?rep_level
; ОШИБКА - "@Until" без открытого оператора "@Repeat"
EXITM
ENDIF
IF (?rep_level LT 10)
; ОШИБКА - "@Until" без открытого оператора "@Repeat"
EXITM
ENDIF
;; Вставить в программный код проверку условия
cmp &p1,&p3
;; Перейти к секции "@Until" .TRUE.
j&p2 iftrue
;; Вставить переход на начало цикла
@MakeJmp ?rep_,%?rep_level,?rep_nest
iftrue:
?rep_level = ?rep_level- 1
ENDM
;;
;; ** @For *********** STRUCTURED CONTROL MACRO *****
;; Структурированное макро "@For". Используйте это
;; макро так:
;; @For counter,begin,end,dir,step
;;
@For MACRO p1,p2,p3,p4,p5
LOCAL first
LOCAL iftrue
IFNDEF ?for_level ;;установить новый
;;уровень вложенности
?for_level = 10
ELSE
?for_level = ?for_level + 1
ENDIF
;; Установить новый номер последовательности для
;; уровня вложенности
@TestSym ?for_nest,%?for_level
IF (?symdef)
@IncSym ?for_nest,%?for_level
ELSE
@ZeroSym ?for_nest,%?for_level
ENDIF
;; Вставить в программный код инициализацию счетчика -
;; (обойти 1-ый шаг)
mov &p1,&p2 ;инициализировать счетчик
jmp first ;начать цикл FOR
;; Вставить метку перехода на начало цикла
@MakeJmpLabel ?for_,%?for_level,?for_nest
;; Вставить в программный код шаг вычислений и выполнить
;; его проверку
IFIDN ,<+>
inc &p1 ;увеличить счетчик
ELSE
IFIDN ,<->
dec &p1 ;уменьшить счетчик
ELSE
; ОШИБКА - неверная спецификация шага в операторе "@For"
- 1-38 -
EXITM
ENDIF
ENDIF
first: ;проверить на необходимость продолжения
;; Вставить в программный код проверку условия
cmp &p1,&p3 ;достигнут ли конец?
;; Перейти к секции "FOR_TRUE"
IFIDN ,<+>
jl iftrue ;нет - продолжить цикл FOR
ELSE ;;по умолчанию - к шагу "-"
jg iftrue ;нет - продолжить цикл FOR
ENDIF
;; Перейти к следующей метке последовательности
@IncSym ?for_nest,%?for_level
;; Вставить в программный код переход на конец цикла
@MakeJmp ?for,%?for_level,?for_nest
iftrue:
ENDM
;;
;; ** @ForEnd ******** STRUCTURED CONTROL MACRO *****
;; Структурированное макро "FOR_END" для совместного
;; использования с "FOR"
;; @ForEnd генерирует программный код для структури-
;; рованного цикла FOR
@ForEnd MACRO
IFNDEF ?for_level
; ОШИБКА - "@ForEnd" без открытого оператора "FOR"
EXITM
ENDIF
IF (?for_level LT 10) page_end12
;
EXITM
ENDIF
;;
@DecSym ?for_nest,%?for_level
;;
@MakeJmp ?for_,%?for_level,?for_nest
;;
@IncSym ?for_nest,%?for_level
;;
@MakeJmpLabel ?for_,%?for_level,?for_nest
?for_level = ?for_level- 1
ENDM
;; **************************************************
Как работают структурированные макросы
Сложность этих макросов вытекает из необходимости поддержки
вложенных структур управления. Рассмотрим пример, приведенный на
Рис.1-2. Каждая конструкция IF-THEN-ELSE требует наличия трех
операторов перехода с тремя уникальными метками. Однако для запо-
минания уникальных меток, сгенерированных директивой LOCAL, мы не
можем использовать символы - нам приходится создавать собственные
метки на базе счетчиков. Это обеспечивает прямое управление зада-
чей.
Для единичных уровней вложенности достаточно простого счетчи-
ка. Обратите внимание, как на Рис. 1-2 конструкция IF-THEN-ELSE,
связанная с условием b, использует последовательность меток
3,4,5. Это реализовать просто, так как метки используются в том
- 1-39 -
же порядке, что и команды перехода, и метки перехода. Однако, как
только появляются вложенные структуры управления, простой счетчик
не справляется. Мимолетный взгляд на последовательность меток для
всех трех операторов IF-THEN-ELSE выявляет серьезный недостаток
последовательности. Данная проблема решается использованием для
каждого уровня вложенности своего счетчика.
Уникальные метки создаются посредством включения в каждую из
них трех элементов информации. Первым элементом является иденти-
фикатор типа структуры, например, ?if_us, do_, ?rep_.Знак вопроса
используется для уменьшения вероятности конфликта с создаваемыми
пользователем символами или метками. Второй элемент информации
представляет собой уровень вложенности, который используется для
различения между номером метки n на одном уровне вложенности и
номером метки n на основном уровне вложенности и номером метки n
на другом уровне вложенности.Наконец, для обеспечения уникальнос-
ти метки каждого перехода конкретного уровня вложенности включа-
ется значение счетчика.
СТРУКТУРА УПРАВЛЕНИЯ НА АССЕМБЛЕРЕ
[ j(a) 1_1:
[ jmp 1_2:
IF(условие а) ----[ L_1: . (а)старт true-
[ . |
. |
[ j(b) 1_3: |
IF(условие b)---[ jmp 1_4: |
[ L_3: (b) кoд true|
. |
. |
. |
[ jmp 1_5: |
ELSE ----------[ L_4: . (b) код false|
[ . |
. |
ENDIF ----------- L_5: (a) конец true-
[ jmp 1_6:
ELSE ------------[ L_2: (a) старт false-
[ . |
. |
. |
[ j(c) 1_7: |
IF(условие с) --[ jmp 1_8: |
[ L_7: (c) код true |
. |
. |
. |
[ jmp 1_9: |
ELSE-----------[ L_8: (c) код false |
[ . |
. |
. |
ENDIF------------- L_9: (a) конец false-
ENDIF---------------- L_6:
Рис.1-2. Структура управления IF и соответствующая ей
интерпретация на языке ассемблера
- 1-40 -
Для сравнения эти уникальные составные метки, сгенерированные
нашими структурированными макросами, показаны в Листинге 1-15.
Первые две цифры числа являются уровнем вложенности, значение ко-
торого начинается с 10 с тем, чтобы для уровня вложенности всегда
были зарезервированы две цифры. Это предотвращает совпадение
уровня 1 счетчика 11 (1-11) с уровнем 11 счетчика 1 (11-1).
Краткий текст программы в точности соответствует тому, что
представлено на Рис. 1-2. При детальном рассмотрении мы увидим,
что расширенные макро на языке ассемблера создают те же структу-
ры, что представлены на Рис.1-2.
Так как метки состоят из трех частей, каждый тип макро струк-
турного управления должен поддерживать набор счетчиков. Этот на-
бор включает в себя символ счетчика для указания текущего уровня
вложенности. Для обобщения задачи сопровождения этих счетчиков мы
создали следующие макросы: testsym, zerosym, incsym и decsym.
Этим макросам передаются аргументы, которые они используют для
создания счетчиков. Аргументы представляют собой идентификаторы
типа (?if_) и текущие уровни вложенности.
Приемы кодирования и некоторые предупреждения
Когда необходимо создать действительную команду перехода и
метку перехода, мы будем использовать макросы mkjmp, mkjmp2,
mklbl и mklbl1. Действительные метки состоят из идентификатора
типа и номеров. Единственный способ получить числовое значение
символа заключается в применении оператора процента (%), который
действителен только при использовании с аргументом вызова макро.
Мы хотим вычислить символ, определяемый двумя элементами информа-
ции из счетчика, так:
mkjmp2 p1,p2,%&p3&p2
Однако Руководство по MASM сообщает нам, что оператор ампер-
санда (&) не может быть использован в вызове макро. Таким обра-
зом, мы должны создать временную переменную и использовать ее.
??tmp = &p3&p2
mkjmp2 p1,p2,%??tmp
Все это влечет за собой следующее. Первая форма, содержащая
амперсанды в вызове макро, должна работать. Однако выбор скрытой
возможности вызывает проблемы будущей совместимости и даже реали-
зуемости. Кроме того, Вы всегда должны задаваться вопросом, может
ли неподдерживаемая или несанкционированная функция зависеть от
реализации совместимости. Решение этой дилеммы остается за чита-
телем.
Авторы использовали эту несанкционированную возможность в
программе, не генерирующей программного кода, но решающей знаме-
нитую задачу "Ханойские башни" в рекурсивной манере. Кроме того,
для достижения общности наш метод создания символов счетчиков из
нескольких частей позволяет при необходимости создавать новые
счетчики. Перед своим использованием эти счетчики должны инициа-
лизироваться, в противном случае первая попытка увеличить или
- 1-41 -
уменьшить их значение вызовет ошибку "Символ не определен". Ис-
пользуя условный оператор IFDEF, можно проверить, требуется ли
инициализация при каждом использовании символа.
Инициализация связана еще с одной тонкостью работы MASM. Как
мы установили, MASM является двухпроходным ассемблером, определя-
ющим символы при первом проходе и затем использующим их при вто-
ром. Это значит, что определения символов защищены от первого
прохода ко второму. Таким образом, когда MASM начинает второй
проход, все счетчики первого прохода уже определены и содержат
свои старые значения. Если в начале второго прохода переинициали-
зация символов не происходит,возникает фазовая ошибка, так как
начальные значения счетчиков отличаются.
Для инициализации символов на первом проходе необходима конс-
трукция IFDEF, так как заранее мы не знаем, сколько счетчиков
потребуется, а использования IFDEF на втором проходе недостаточ-
но. Мы решили эту проблему, создав символы ?р2sw ..., которые на
втором проходе анализируются на необходимость установки в нулевые
значения. Имя получается из Switch (переключателя) фазы 2. Этот
процесс проверки предоставляет хорошую возможность выявить, при-
надлежат ли уровни вложенности самому верхнему уровню, указывая,
что конструкции IF-IFEND, DOWHILE-DOEND и т.д. спарены правильно.
В Листинге 1-16 приведены простые примеры расширения макросов
структурного управления, определенных ранее. Как можно видеть, мы
подавили те части расширения, которые не вырабатывают код или
метки переходов. Если Вы хотите ознакомиться с работой этих мак-
росов более детально, используйте директиву .LALL. Прибегайте
только к сокращенному примеру, так как обработка этих макро вызы-
вает выполнение множества шагов. Количество шагов также объясня-
ет, почему происходит увеличение требуемого времени ассемблирова-
ния программы. Используя эти макро, не ожидайте быстрого
ассемблирования, но рассчитывайте на быстрое кодирование.
Листинг 1-15. Вложенная структура IF-THEN-ELSE
----------------------------------------------------------------
; Сжатый исходный код программы
@IfTrue e условие (a)
@IfTrue e условие (b)
@IfElse "else" для условия (b)
@IfEnd конец условия (b)
@IfElse "else" для условия (a)
@IfTrue e условие (c)
@IfElse "else" для условия (c)
@IfEnd конец условия (c)
@IfEnd конец условия (a)
; Листинг расширения
@IfTrue e условие (a)
1 je ??0000
3 jmp ?if_100
1 ??0000:
; выполнить, если условие (а) истинно
@IfTrue e условие (b)
1 je ??0001
3 jmp ?if_110
- 1-42 -
1 ??0001:
; выполнить, если условие (b) истинно
@IfElse "else" для условия (b)
3 jmp ?if_111
3 ?if_110:
; выполнить, если условие (b) не истинно
@IfEnd конец условия (b)
3 ?if_111:
@IfElse "else" для условия (a)
3 jmp ?if_101
3 ?if_100:
; выполнить, если условие (а) не истинно
@IfTrue e условие (c)
1 je ??0002
3 jmp ?if_112
1 ??0002:
; выполнить, если условие (с) истинно
@IfElse "else" для условия (c)
3 jmp ?if_113
3 ?if_112:
; выполнить, если условие (с) не истинно
@IfEnd конец условия (c)
3 ?if_113:
@IfEnd конец условия (a)
3 ?if_101:
---------------------------------------------------------------
Листинг 1-16. Расширение структурированных макросов
----------------------------------------------------------------
@IfTrue e
1 je ??0000
3 jmp ?if_100
1 ??0000:
; Выполнить, если истина
@IfElse
3 jmp ?if_101
3 ?if_100:
; Выполнить, если не истина
@IfEnd
3 ?if_101:
;- - - - - - - - - - - - - - - - - - - - - - - - - -
@DoWhile ax,le,bx
3 ?do_100:
1 cmp ax,bx
1 jle ??0001
3 jmp ?do_101
1 ??0001:
; Выполнять пока ax <= bx
@DoExit
3 jmp ?do_101
; Выйти из программы
@DoEnd
3 jmp ?do_100
3 ?do_101:
;- - - - - - - - - - - - - - - - - - - - - - - - - - - -
- 1-43 -
@Repeat
3 ?rep_100:
; Выполнять пока соблюдается условие
@Until ax,e,bx
1 cmp ax,bx
1 je ??0002
3 jmp ?rep_100
1 ??0002:
;- - - - - - - - - - - - -- - - - - - - - - - - - - - -
@For ax,10,20,+
1 mov ax,10 ;инициализировать счетчик
1 jmp ??0003 ;начать цикл FOR
3 ?for_100:
1 inc ax ;увеличить счетчик
1 ??0003: ;проверить на необходимость продолжения
1 cmp ax,20 ;достигнут ли конец
1 jl ??0004 ;нет - продолжить цикл
3 jmp ?for_101
1 ??0004:
; Выполнить для ах = 10 до 20 с шагом 2
@ForEnd
3 jmp ?for_100
3 ?for_101:
----------------------------------------------------------------
Макро псевдо-CASE
Последнее макро, которое мы ввели в этой главе,есть макро
псевдо-case, представленное Листингом 1-17. Так как макро должно
иметь "предвидение" о поддерживаемых в нем структурах, мы не бу-
дем рассматривать его как оператор структурного управления. По
своим функциям макро case более походит на блок диспетчеризации,
типа вычисляемого оператора GOTO в языке Фортран.
Листинг 1-17. Описание макро псевдо-саse
----------------------------------------------------------------
@Case MACRO кey,сase_list,jmр_labels
??tmp_1 = 0
IRP match,<&case_list> ;;последователь-
;;ность вариантов
??tmp_1= ??tmp_1 + 1 ;;установить номер
;;индекса
cmp key,&&match ;вариант найден?
??tmp_2= 0
IRP retl,<&jmp_labels> ;;последователь-
;;ность переходов
??tmp_2=0 ??tmp_2 + 1 ;; до достижения
;; индекса
IF (??tmp_1 EQ ??tmp_2)
je &&&retl ; Да!
EXITM
ENDIF ;;конец проверки условия
ENDM ;;закончить 2-ой блок IRP
ENDM ;;закончить 1-ый блок IRP
ENDM ;;закончить макроописание
---------------------------------------------------------------
- 1-44 -
Это макро являет собой хороший пример одновременного синтаксичес-
кого анализа двух списков. Внешний цикл, irp match,<&case_list>,
устанавливает последовательность элементов списка вариантов, а
внутренний цикл, irp retl,<&jmp_labels>, выбирает соответствующие
метки переходов.Такое решение может быть использовано для реали-
зации макросов подстановки.
В макросах подстановки внешний цикл устанавливает последова-
тельность элементов списка и выявляет совпадение. После определе-
ния совпадения, скажем на элементе xth, макро входит во внутрен-
ний цикл и устанавливает последовательность элемента xth этого
списка. Одним из возможных вариантов реализации может быть созда-
ние макро переход_по_невыполнению_условия, где выбранный переход
должен был бы заменяться на противоположный. Еще раз напомним,
что во вложенных макроблоках необходимо использовать дополнитель-
ные амперсанды.
Расширение макро @Case представлено в Листинге 1-18. За то,
чтобы в каждом списке появлялся один и тот же номер элемента, от-
вечает программист. В противном случае может быть получена невер-
ная структура управления.
Листинг 1-18. Расширение макро псевдо-@Case
----------------------------------------------------------------
@Case al,<'A','B','C','D'>,
2 cmp al,'A' ;вариант соответствует?
3 je subA ; да!
2 cmp al,'B' ;вариант соответствует?
3 je subB ; да!
2 cmp al,'C' ;вариант соответствует?
3 je subC ; да!
2 cmp al,'D' ;вариант соответствует?
3 je subD ; да!
subA:
jmp merge
subB:
jmp merge
subC:
jmp merge
subD:
jmp merge
default:
merge:
----------------------------------------------------------------
Макросы данных
Макросы могут использоваться для генерации данных или прог-
раммного кода. В обоих случаях подход и методы одинаковы, однако,
в целях изучения рассмотрим макросы, генерирующие только данные.
Простейшим примером команды MASM, генерирующей данные, являет-
ся:
- 1-45 -
TenBytes DB 10 DUP 4 ;зарезервировать 10
;байтов под номером 4
Эта команда имеет ограниченное применение, так как она пред-
почтительна, когда мы хотим получить последовательность чисел как
в индексном наборе. В качестве примера зарезервируем N слов дан-
ных в наборе чисел от 1 до N:
@FirstTry MACRO N ;;определить макро с параметром N
NUMB = 0 ;;инициализировать число
REPT N ;;повторить нижеследующее N раз
NUMB = NUMB+1 ;; увеличить индекс
DW NUMB ;;определить слово NUMB
ENDM ;;закончить команду REPT
ENDM ;;конец макро
Заметим, что для каждой директивы MACRO должно присутствовать
ENDM. Первой переменной, NUMB, значение присваивается оператором
=, а не EQU, что позволяет изменять ее значение в блоке REPT.
Директива REPT представляет собой циклическую структуру, типа
do...while языка высокого уровня. Она повторяет действия, заклю-
ченные между REPT и ENDM, N раз. В данном случае происходит уве-
личение NUMB на 1, а затем создается слово, содержащее это число.
(Имейте в виду, что Вы указываете MASM на создание констант, ко-
торые будут ассемблироваться. Вы не указываете компьютеру цикл,
подлежащий проходу во время выполнения программы)
Если макроописание FirstTry поместить в начало нашей програм-
мы, а затем использовать его в сегменте данных с N, равным 4, мы
получим:
@FirstTry 4
что соответствует тому, что MASM будет ассемблировать четыре сло-
ва чисел от 1 до 4.
Это слишком простой пример использования макро. Разрешите ус-
ложнить его, создав таблицу двоично-десятичных чисел, которая мо-
жет служить таблицей просмотра при преобразовании шестнадцатирич-
ных данных в код BCD.
@BCDtable MACRO N ;;определить макро с параметром N
NUMB = 0 ;;инициализировать числа
HIGHBYTE = 0
REPT N ;;повторить нижеследующее N раз
NUMB = NUMB+1 ;;увеличить индекс
IF (NUMB GT 9)
NUMB = 0
HIGHBYTE = HIGHBYTE + 10H
ENDIF
IF (HIGHBYTE GT 90H)
EXITM
ENDIF
BCDNUMB = (NUMB OR HIGHBYTE)
DW BCDNUMB ;;определить слово с именем NUMB
ENDM ;; конец команды REPT
ENDM ;; конец макро
- 1-46 -
Этот пример значительно сложнее, но он не представляет ничего
особенного для опытного программиста. Прежде чем провести пост-
рочный анализ этих директив (термин "директива" мы используем для
обозначения того, что является командой для MASM, а не для ЦП),
разрешите рассмотреть результат работы программы с N, установлен-
ным в 20:
38 @BCDtable 20
39 0004 0001 2 DW BCDNUMB ;
40 0006 0002 2 DW BCDNUMB ;
41 0008 0003 2 DW BCDNUMB ;
42 000A 0004 2 DW BCDNUMB ;
43 000C 0005 2 DW BCDNUMB ;
44 000E 0006 2 DW BCDNUMB ;
45 0010 0007 2 DW BCDNUMB ;
46 0012 0008 2 DW BCDNUMB ;
47 0014 0009 2 DW BCDNUMB ;
48 0016 0010 2 DW BCDNUMB ;
49 0018 0011 2 DW BCDNUMB ;
50 001A 0012 2 DW BCDNUMB ;
51 001C 0013 2 DW BCDNUMB ;
52 001E 0014 2 DW BCDNUMB ;
53 002O 0015 2 DW BCDNUMB ;
54 0022 0016 2 DW BCDNUMB ;
55 0024 0017 2 DW BCDNUMB ;
56 0026 0018 2 DW BCDNUMB ;
57 0028 0019 2 DW BCDNUMB ;
58 002A 0020 2 DW BCDNUMB ;
Первый столбец представляет собой номера строк ассемблерного
листинга, второй - смещение адреса относительно модуля, а третий
- то, что мы хотели - таблицу чисел BCD от 1 до 20.
Теперь построчно рассмотрим все макро. Прежде всего мы инициа-
лизируем две переменные. NUMB будет зациклена от 1 до 9, предс-
тавляя младший байт, в то время как HIGHBYTE будет представлять
байт более высокого порядка. Остальной частью макро управляет ди-
ректива REPT. Первым делом внутри блока повторения мы увеличиваем
на 1 переменную NUMB. Затем определяем, равна ли она 10, и, если
это так, для очередного запуска цикла устанавливаем ее в 0. Затем
добавляем к HIGHBYTE - 10, увеличивая цифру десятков числа в фор-
мате BCD. Далее завершаем оператор IF.
Следующим шагом проверяем, если построенное нами число в BCD
больше того, что может храниться в слове, то выходим из макро.
Предпоследним действием создаем число в BCD, выполняя операцию
логического "ИЛИ" над цифрой единиц и цифрой десятков. Наконец,
создаем слово, содержащее требуемое число в BCD. Первый ENDM за-
вершает цикл REPT; второй - завершает макро. Для ссылки к списку
чисел BCD необходима метка. Мы не хотим набирать метку каждый
раз, когда обращаемся к макро. Мы используем оператор подстановки
@, чтобы эту метку создавал MASM:
@BCDtable MACRO N ;;определить макро с параметром N
BCD1to&N label word ;;определить метку
NUMB = 0 ;;инициализировать числа
HIGHBYTE = 0
REPT N ;;повторить нижеследующее N раз
- 1-47 -
NUMB = NUMB+1 ;;увеличить индекс
IF (NUMB GT 9)
NUMB = 0
HIGHBYTE = HIGHBYTE + 10H
ENDIF
IF (HIGHBYTE GT 90H)
EXITM
ENDIF
BCDNUMB = (NUMB OR HIGHBYTE)
DW BCDNUMB ;;определить слово с именем NUMB
ENDM ;;конец команды REPT
ENDM ;;конец макро
Теперь в листинге наше макро выглядит так:
31 @BCDtabel 20
32 0004 1 BCD1to20 label word ;определить метку
33 0004 0001 2 DW BCDNUMB ;
34 0006 0002 2 DW BCDNUMB ;
35 0008 0003 2 DW BCDNUMB ;
и т.д.
Знак амперсанда (&) сообщает MASM о необходимости подставить
значение N, используемое при инициировании макро. Но и это нас
еще не удовлетворяет. Наличие у списка чисел BCD только одной
метки заставляет нас для доступа к списку использовать индекс.
Мы бы хотели иметь метку у каждого элемента списка. Оператор вы-
ражения % позволит нам иметь значение каждого числа и использо-
вать его как часть метки. Мы переписываем наше макро в виде двух
следующих макро:
@BCD MACRO NAME,NUMBER ;;NAME для метки
;;NUMBER для данных
BCDof&NAME DW NUMBER ;;определить слово с
;;NUMBER в коде BCD
ENDM ;;конец макро
;;
@BCDtable MACRO N ;;определить макро с параметром N
NUMB = 0 ;;инициализировать числа
INDEX = 0
HIGHBYTE = 0
REPT N ;;повторить нижеследующее N раз
INDEX = INDEX + 1
NUMB = NUMB + 1 ;;увеличить индекс
IF (NUMB GT 9)
NUMB = 0
HIGHBYTE = HIGHBYTE + 10H
ENDIF
IF (HIGHBYTE GT 90H)
EXITM
ENDIF
BCDNUMB = (NUMB OR HIGHBYTE)
@BCD %INDEX,BCDNUMB ;;INDEX для метки
;;BCDNUMBER для данных
ENDM ;;конец команды REPT
ENDM ;;конец макро
- 1-48 -
Теперь, когда мы смотрим листинг, мы видим, что каждый байт
таблицы чисел BCD имеет соответствующую метку:
@BCDtable 20
0004 0001 3 BCDof1 DW BCDNUMB ;определить число с
0006 0002 3 BCDof2 DW BCDNUMB ; NUMBER
0008 0003 3 BCDof3 DW BCDNUMB ; в
000A 0004 3 BCDof4 DW BCDNUMB ; коде BCD
.
.
.
Таким образом можно создавать сложные таблицы.Если есть форму-
ла типа (N x M)/((P+Q) MOD T), вместо ручного заполнения таблицы
мы можем поручить заботы по ее созданию MASM.
Проверять ситуацию переполнения мы могли бы, включив в текст
нашего макро что-нибудь вроде следующего:
IFE (BCDNUMB LE 0FFFFh) ;;больше, чем может хранить слово?
DW BCDNUMB ;;достаточно мало
ELSE
%OUT ERROR IN @BCD MACRO
Оператор OUT выводит сообщение на экран во время ассемблирова-
ния - в данном случае сообщение "ERROR IN @BCD MACRO" (ошибка в
макро @BCD).
До сих пор мы использовали параметры, как конкретные элементы,
разделенные запятыми. В качестве одиночного параметра макро также
возможно иметь набор элементов, который будет использоваться для
итеративного создания данных. Например, если мы хотим установить
список сообщений, подлежащих выводу на экран, мы можем закодиро-
вать макронабор:
@OptDisp MACRO OptType,Options ;; OptType = метка,
;; Options = список
OptType&list db Options
ENDM ;;конец макро
Затем мы можем использовать егo в сегменте данных:
@OptDisp LineSpeed,<'2400sq],'2400','4800'>
Linespeed - будет заменено в метке, и каждая строка в угловых
скобках будет вставлена в директиву db, как если мы набрали:
LineSpeedList db '1200'
db '2400'
db '4800'
Подобное применение ограничено, так как при доступе к строке
мы исходим из знания, что она имеет в длину 4 символа. Значитель-
но чаще мы имеем дело со строками переменной длины, заканчивающи-
мися нулем в коде ASСII. Ниже приводится макро создания таких
строк:
- 1-49 -
@MakeList MACRO Name2,messag
MESSAGE&Name2 db CR,LF,messag,CR,LF,0
ENDM
;;
@OptDisp MACRO Options ;;OptType = метка,Options = список
Name3 = 0
IRP msg,
Name3 = Name3 + 1
@MakeList %Name3,msg
ENDM
ENDM ;;конец макро
Строки сегмента данных мы можем использовать так:
@OptDisp <'Error','Waiting','Computing'>
Каждая строка в угловых скобках будет помещаться в дирeк-
тиву db, как показано в следующем фрагменте листинга:
@OptDisp <'Error','Waiting','Computing'>
0D 0A 45 72 72 6F 72 3 MESSAGE1 db CR,LF,'ERROR',CR,LF,0
0D 0A 57 61 69 74 69 3 MESSAGE2 db CR,LF,'Waiting',CR,LF,0
0D 0A 43 6F 6D 70 75 3 MESSAGE3 db CR,LF,'Computing',CR,LF,0
Поучительным моментом данного макро является то, что для соз-
дания необходимого числа строк мы использовали в директиве IRP
оператор литерального текста (< >).Однако у нас еще не решена
проблема доступа к этому списку строк. Нам необходим список адре-
сов. Следующее макро представляет решение этой проблемы.
@MakeList МACRO Name2,messag
MESSAGE&Name2 db CR,LF,messag,CR,LF,0
ENDM
;;
@MakeNames MACRO Name5
db MESSAGE&Name5
ENDM ;;конец REPT
;;
@OptDisp MACRO Options ;;OptType = метка, Options = список
Name3 = 0
IRP msg,
Name3 = Name3 + 1
@MakeList %Name3,msg
ENDM
Name4 = 0
MessageList Label WORD
REPT Name3
Name4 = Name4 + 1
@MakeNames %Name4
ENDM ;;конец REPT
ENDM ;;конец макро
Когда макро используется в секции данных, мы получим тот же
результат, как если бы набрали следующее:
@OptDisp <'Error','Waiting','Computing'>
MESSAGE1 db CR,LF,'Error',CR,LF,0
MESSAGE2 db CR,LF','Waiting',CR,LF,0
MESSAGE3 db CR,LF,'Computing',CR,LF,0
MessageList Label WORD
dw MESSAGE1
dw MESSAGE2
dw MESSAGE3
- 1-50 -
Конечно, для генерации данных при помощи макро можно сделать
гораздо больше. Мы лишь подбросили идею некоторых возможностей.
Те же методы могут быть использованы для генерации программного
кода, что мы теперь и рассмотрим.
Макросы генерации программного кода
Макросы представляют собой мощный механизм передачи ассемблеру
некоторых действий по программированию. Также, как Вы можете пи-
сать программу на Бейсике, заставляющую компьютер выполнять за-
данную Вами работу, Вы можете написать программу на МAСRO, зас-
тавляющую ассемблер, в данном случае MASM, выполнять для Вас
неинтересную часть работы по программированию. Ниже, в качестве
упрощенного примера того, что мы имели в виду, приведено макро,
реализующее запись символа в файл:
@WritToFil MACRO ;;определить макро
mov ah,40h ;;функция DOS по записи в файл
int 21h ;;вызов DOS
ENDM ;;конец макро
Теперь вместо того, чтобы каждый раз, когда мы хотим записать
символ в файл, переписывать команды MOV и INT, мы можем использо-
вать макро WritToFil.
ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
і Сравнительные характеристики макросов и подпрограмм і
і і
і При помощи подпрограмм мы можем сделать то же самое, что иі
іпри помощи макро, однако оформление небольших программных кус-і
іков в подпрограммы не эффективно. Разница между макро и под- і
іпрограммой заключается в том, что макро вставляет нужный прог-і
іраммный код непосредственно в файл исходного текста, в то времяі
ікак подпрограмма размещается где-нибудь в другом месте, и дляі
івыполнения ее программного кода мы должны осуществлять переході
іпо месту ее расположения. Другими словами, использование макро-і
ісов для создания повторяющегося линейного кода ликвидирует нак-і
іладные расходы при выполнении программы, обусловленные вызовомі
іи возвратом из подпрограмм. і
і Мы используем макросы вместо подпрограмм из тех же соображе-і
іний, что для короткого разговора обращаемся к кому-либо по те-і
ілефону вместо поездки к нему через весь город - потери времениі
іпри переходе по другому адресу не оправдываются краткостью на-і
ішей задачи. Кроме того, макрокод имеет тенденцию к уменьшениюі
ісвоих размеров, так как он добавляется к программе всякий раз,і
ікогда должен использоваться. Если он получается слишком длин-і
іным, его следует оформить в подпрограмму. Что значит "Слишкомі
ідлинный"? Это зависит от накладных расходов, необходимых на вы-і
ізов подпрограммы, от того, как часто используется функция, и оті
іотношения значения памяти к скорости выполнения программы. і
і Макросы работают быстрее, так как не требуют сохранения ре-і
ігистров,помещения в стек параметров и т.д. Однако частые повто-і
ірения коротких макросов могут занимать значительную память ві
іобъектных и исполнимых файлах. Сначала напишите макрос и, еслиі
іокажется, что он становится неуправляем, перепишите его в под-і
іпрограмму. Позже мы увидим,как можно оформить вызов подпрограммі
ів виде макро. і
АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
- 1-51 -
Условные макросы
Приведенный выше пример макрокода слишком прост, поэтому раз-
решите несколько "приодеть его". Предположим, что для целей от-
ладки мы хотели бы писать наши символы на экран, а не в файл.
Тогда мы можем переписать макро следующим образом:
@WritToFil MACRO EКOFLAG ;;определить INCHRIF с
;;аргументом EKOFLAG
IFIDN , ;;если аргумент EKOFLAG
;;идентичен трем буквам
;;EKO, ассемблировать
;;следующую строку
mov ah,06h ;;функция DOS по записи в
;;стандартный вывод
ELSE ;;если EKOFLAG не идентично трем буквам
;; EKO, ассемблировать следующую строку
mov ah,40h ;;функция DOS по записи в файл
int 21h ;;вызов DOS
ENDM ;;конец макро
В данном случае MASM анализирует аргумент EKOFLAG с целью оп-
ределения, что вставлять: mov ah,06h или mov ah,40h:
@WritToFil EKO ;здесь MASM подставляет MOV AH,06 и
;INT 21H
.
.
. ;так как аргумент идентичен EKO
@WritToFil NOEKO ;MASM подставляет MOV AH,40H и
;INT 21H
.
.
. ;так как аргумент не идентичен EKO
Заметим, что в предыдущем примере вместо NOEKO мы могли бы ис-
пользовать PHUBAH или что-нибудь еще, так как основная мысль зак-
лючается в том, чтобы аргументом не было EKО. Спеллинг параметра
довольно произвольный. Это гарантирует возможность ошибки, если
мы забудем его и напишем @WritToFil ECHO. Такая запись лишает нас
появления эха на экране, так как вместо EKO мы указали ECHO. Мы
можем исключить возможность появления такой ошибки, ограничив се-
бя использованием EKO или NOEKO:
@WritToFil MACRO EKOFLAG ;;определить INCHRIF c аргументом
;;EKOFLAG
IFIDN , ;;если EKOFLAG = EKO,
- 1-52 -
;;ассемблировать след-ую строку
mov ah,06h ;;функция DOS по записи в
;;стандартный файл
ELSE ;;в противном случае
IFIDN , ;;если EKOFLAG = NOEKO,
;;ассемблировать
mov ah,40h ;;функция DOS по записи в файл
ELSE ;;если аргумент не соответствует,
;; то
.ERR ;;выдать ошибку ассемблирования
ENDIF ;;конец проверки условия
int 21h ;; вызов DOS
ENDM ;; конец макро
Вложенные макросы
Рассмотренный нами макрос использует функцию DOS по записи
символов на стандартное устройство вывода или в файл. Однако мы
можем захотеть проверить, была ли нажата клавиша для прерывания
вывода, и если это не так, продолжить обработку. Функция DOS 0Bh
проверяет, была ли нажата клавиша, возвращая AL = 0FFh, если сим-
вол доступен, и AL = 00, если символ не доступен. Мы не можем на-
писать макро chkchr и затем вызывать его из нашего макроса
WritToFil:
@ChkChr MACRO ;;определить макро @ChkChr
mov ah,0Bh ;;проверить стандартный ввод
int 21h ;;вызов DOS
ENDM ;;конец макро
;;
@WritToFil MACRO WAITFLAG,EKOFLAG ;; 2 аргумента
LOCAL bye ;;определить формальный адрес
IFNB ;;если поле для WAITFLAG не
;;пусто, ассемблировать
;;следующее
@ChkChr ;;выявить, ожидается ли символ
cmp al,0 ;; al = 0 => символ не ожидается
je bye ;;если символа нет, продолжить
ENDIF ;;конец проверки условия
IFIDN , ;;если EKOFLAG=EKO, ас-
;;семблировать
MOV AH,06H ;;функция DOS по записи
;;в стандартный вывод
ELSE ;;в противном случае
IFIDN <ЕKOFLAG>, ;;если EKOFLAG=NOEKO,
;;ассемблировать
MOV AH,40H ;;функция DOS по записи в файл
ELSE ;;если аргумент не соответствует
.ERR ;;выдать ошибку ассемблирования
ENDIF ;;конец проверки условия
int 21h ;;вызов DOS
bye:
ENDM ;;конец макро
Обсудим некоторые возможности новой версии WritToFil. Директи-
ва LOCAL сообщает MASM, что метка bye является формальной мет-
- 1-53 -
кой,которую MASM заменяет на реальную всякий раз, когда макро вы-
зывается из программы. Это устраняет проблему использования одной
и той же метки в программе дважды, что вызвало бы ошибку ассемб-
лирования. MASM ассемблирует макро, используя в первый раз метку
??0000, во второй раз - ??0001 и т.д. до ??FFFFh, что позволяет
вызвать макро в одной программе до 65536 раз. Директива LOCAL
должна следовать сразу же за директивой MACRO - перед ней не мо-
жет стоять даже комментарий! Конструкция IFNB WAITFLAG сообщает
MACRO о необходимости ассемблирования следующих трех строк только
тогда, когда аргумент WAIT-FLAG не пуст. В противном случае прог-
раммный код включен не будет, и первой ассемблируемой строкой бу-
дет одна из строк блока IFIDN. Это предоставляет нам возможность
генерации программного кода, который или будет включаться в ито-
говую программу, или после проверки ключей будет опускаться. Опе-
ратор IFNB проверяет существование WAITFLAG не по спеллингу, та-
ким образом мы можем вызвать макро одной из следующих команд:
@WritToFil WAIT,EKO
@WritToFil WAITE,EKO
@WritToFil NoWate,EKO
@WritToFil FOOBAH,EKO
и затем генерировать код, который "не ждет" ввода. Заметим также,
что мы получили вложенность макро, когда одно макро вызывает дру-
гое.
Несколько слов о возможностях макро
Вместо использования параметра WAITFLAG для определения, ас-
семблировать программный код для его включения или нет, мы также
можем использовать глобальную опцию, позволяющую осуществлять
этот выбор во время ассемблирования. Например, мы хотим проверить
нажатие клавиши, если находимся в режиме отладки или если уста-
новлен параметр WAITFLAG, но ни при каких-либо других условиях.
Новое макроописание таково:
@WritToFil MAСRO WAITFLAG,EKOFLAG
LOCAL bye ;;определить формальный адрес
;;макро приема символа из стандартного ввода
;;2 аргумента: WAITFLAG и EKOFLAG определяют,
;;ждать символ или отобразить ввод
.ХCREF ;;подавить выдачу перекрестных ссылок
;;локальных меток и т.д.
x = 0 ;; х - индикатор
IFNDEF DEBUG ;;если параметр DEBUG не опре-
x = 1 ;;делен, установить флаг в 1
ENDIF ;;конец проверки условия
IFNB ;;если поле для WAITFLAG
x = 2 ;;не пусто, флаг = 2
ENDIF ;;конец проверки условия
IF (х EQ 1) or (x eq 2) ;;если не определен
;;DEBUG или не пуст WAITFLAG
@ChkChr ;;проверить, ожидается ли символ
cmp al,0 ;;al = 0 => символ не ожидается
je bye ;;если символа нет, продолжить
ENDIF ;;конец проверки условия
IFIDN , ;;если EKOFLAG = EKO,
- 1-54 -
;;ассемблировать следующую строку
mov ah,06h ;;функция DOS по записи в
;;стандартный вывод
ELSE ;;в противном случае
IFIDN , ;;если EKOFLAG=NOEKO,
;;ассемблировать
mov ah,40h ;;функция DOS по записи в файл
ELSE ;;если аргумент не соответствует
.ERR ;;выдать ошибку ассемблирования
%OUT Ошибка в макро @WritToFil - EKOFLAG не найден
ENDIF ;;конец проверки условия
ENDIF ;;конец проверки условия
int 21h ;;вызов DOS
bye:
.CREF ;;восстановить выдачу перекрестных ссылок
ЕNDМ ;;конец макро
Теперь во время ассемблирования для определения режима DEBUG
мы можем использовать опцию /d:
MASM myprgm,,,; /dDEBUG
и все вызовы макро WritToFil будут генерировать программный код,
проверяющий ввод.
Для определения, ждем ли мы появление символа, мы используем
флаг (с оператором =, а не equ, Так как мы переопределяем его в
следующих двух операторах IF). Вместо (x eq 1 ) или (x eq 2) мы
могли бы закодировать x gt 0 или x NE 0, тaк как действительно
любое значение, отличное от задаваемого при инициализации (0).
Заметим, что мы также добавили несколько новых директив. Символы
;; сообщают MASM, что комментарии не должны появляться в листинге
ассемблера. Директива .ХСREF экономит время ассемблирования и па-
мять для листинга перекрестных ссылок, сообщая MASM, что не нужно
загромождать этот листинг именами, используемыми только в макро.
Директива .СREF восстанавливает выдачу перекрестных ссылок для
оставшейся части листинга. Кроме того, ее можно и не указывать.
Мы также добавили директиву %OUT, которая будет выводить на экран
вставленное в нее сообщение об ошибке. Теперь мы поэксперименти-
руем с некоторыми дополнительными возможностями.
Макро, вызывающее подпрограммы
Одно из наиболее мощных применений макро заключается в универ-
сальном вызове подпрограмм аналогично вызовам подпрограмм в язы-
ках высокого уровня. Задача заключается в проталкивании несколь-
ких параметров в стек и вызове подпрограммы. Довольно просто, за
исключением потребности макро приспособиться к переменному числу
параметров, которые в свою очередь могут иметь переменные размеры
(байт,слово, двойное слово, четвертное слово и 10-байтовые значе-
ния с плавающей точкой). Для выполнения этих требований мы ис-
пользуем операторы .TYPE и TYPE (обратите внимание на точку перед
первым оператором). Использование оператора .TYPE позволяет макро
поддерживать как регистр типа BX, так и слово или байт данных.
Конструкция .TYPE х возвращает байт, набор битов которого содер-
жит следующую информацию:
- 1-55 -
БИТ 0 = 1, если х программно зависимо, иначе = 0
БИТ 1 = 1, если х зависимо от данных, иначе = 0
БИТ 5 = 1, если х определено, иначе = 0
БИТ 7 = 1, если х - внешний параметр, если локальный
или общий = 0
Все остальные биты нулевые.
Например, если х зависимо от данных, определено и локально,
оператор.TYPE х возвращает значение 001000100b (или 22h); -- ус-
тановлены биты 1 и 5. Так как мы хотим разрешить использование
регистров (программно зависимых) в качестве параметров, мы будем
применять оператор .TYPE для сообщения о наличии параметров, за-
висящих от данных. Так как мы хотим поддерживать данные различной
длины отдельно, мы используем оператор TYPE, возвращающий длину
их аргументов в байтах. Например,
TYPE N = 1, если N - байт
TYPE N = 2, ecли N - слово
TYPE N = 4, ecли N - двойное слово
TYPE N = 8, если N - четверное слово
TYPE N = 10, если N - десятибайтовое слово (т.е. с плаваю-
щей точкой)
TYPE N = XX, если N - cтруктура длиной в хх байтов
TYPE N = FFFF,если N - "близкая" программная метка
TYPE N = FFFE,если N - "удаленная" программная метка
Следующее макро иллюстрирует использование директив TYPE и
.TYPE:
@FcnCall MACRO Fnctn,ParmList ;;список подпро-мм и парам-ов
IRP N, ;;неопределен. повторение
BYTELENGTH = TYPE N ;;получить длину "проталкива-
;;емых" элементов в байтах
IF ((.TYPE N ) NE 22H) ;;N определено и зави-
;;симо от данных?
push N ;;если нет - предположить 16-битовый регистр ;;
ELSE ;;в противном случае предположить данные
IF (BYTELENGTH EQ 2) ;;тогда, если параметр 2-
;;байтовый,
push N ;;протолкнуть
ELSE ;;в противном случае
IF (BYTELENGTH EQ 1) ;;если параметр 1-байтовый,
;;предположить, что AX доступен
mov ah,0 ;;очистить верхнюю часть AX
mov al,N ;;сделать параметр словом
push ax ;;так, чтобы мы могли продвинуть его
ELSE ;;в противном случае
IF (BYTELENGTH EQ 4) ;;если параметр 4-байтовый
push word ptr N ;;продвинуть 1-ое и
push word ptr N + 2 ;;2-ое слово
ELSE ;;в противном случае
IF (BYTELENGTH EQ 8) ;;если параметр 8-байт.
push word ptr N ;;продвинуть 1-ое,
push word ptr N + 2 ;; 2-ое
push word ptr N + 4 ;; 3-ье и
push word ptr N + 6 ;; 4-ое слово
ELSE ;;в противном случае
IF (BYTELENGTH EQ 10) ;;если параметр
;;10-байтовый, продвинуть
- 1-56 -
push word ptr N ;; 1-ое
push word ptr N + 2 ;; 2-ое
push word ptr N + 4 ;; 3-ье
push word ptr N + 6 ;; 4-ое и
push word ptr N + 8 ;; 5-ое слово
ELSE
.ERR
ENDIF
ENDIF
ENDIF
ENDIF
ENDIF
ENDIF
call Fnctn
ENDM ;;конец IRP
ENDM ;;конец макро
Замечательным преимуществом данного макро является то, что мы
заранее не указываем количество параметров, которое хотим перес-
лать подпрограмме, до непосредственного обращения к ней. Мы можем
вызвать одну подпрограмму с тремя параметрами, а другую - с дву-
мя. Например:
@FcnCall Fcn1,
@FcnCall Fcn2,
Для любого вызова подпрограммы мы можем иметь фактически неог-
раниченное число параметров.
В данном макро имеется много недостатков. Одним из этих недос-
татков является то, что мы не покрыли все возможные значения
BYTELENGTH, типа программных меток и структур; мы предположили,
что регистр AX доступен только для однобайтового параметра и т.д.
Для большинства этих недостатков существует дилемма: цикл, на ба-
зе BYTELENGTH, мог бы поддерживать все возможные длины данных, но
при этом могли возникнуть другие проблемы, поэтому мы даже не
рассматриваем альтернативы, а считаем своей задачей лишь "протал-
кивание" данных в вызываемую подпрограмму! Пример служит для ил-
люстрации директив TYPE и .TYPE, однако рассмотрение общецелевой
функции вызова подпрограмм требует нечто большего. Прежде чем
продолжить разбор этого макро мы сделаем короткое отвлечение на
введение понятия структуры.
Применение директивы STRUC
Структуры представляют собой директивы ассемблера, позволяющие
нам строить сложные форматы данных, состоящие из байтов, слов и
т.д., таким образом, чтобы они имели большую смысловую вырази-
тельность и доступность. Они очень похожи на структуры Си и запи-
си Паскаля. А отличаются они тем, что в МASM индексирование зат-
руднено, вложенность вообще запрещена. В качестве примера,
который мы можем использовать в макросе, передающем параметры,
разрешите предположить, что Вы написали программу, выполняющую
математические функции. Ниже приводится структура, которую Вы
могли бы создать:
- 1-57 -
MathNumbers STRUC
BooLean1 DB (0) ; 1 байт
BooLean2 DB (0) ; 1 байт
ShortInteger1 DW (0) ; 1 слово
ShortInteger2 DW (0) ; 1 слово
LongInteger1 DD (0) ; 1 двойное слово
LongInteger2 DD (0) ; 1 двойное слово
Float1 DT (0) ; 1 10-байтовое слово
; (для 8087)
Float2 DT (0) ; 1 10-байтовое слово
; (для 8087)
MathNumbers ENDS
MathNumbers определяет тип структуры. STRUС и ENDS ограничива-
ют начало и конец описания структуры. Теперь мы можем использо-
вать MаthNumbers для объявления некоторых данных, например, так:
TrueFalse MathNumbers <1,0,,,,,,>
MaxMinShort MathNumbers <,,32767,-32768,,,,>
MaxMinLong MathNumbers <,,,,2147483647,-2147483648,,>
e MathNumbers <,,,,,,,2.718281828>
ListLength = 100
MathList MathNumbers ListLength dup <,,,,,,,>
Память зарезервирована под 104 числа. При 34 байтах под число
наш список чисел займет 3536 байтов. Первоначально структура в
своем описании инициализируется в 0, а затем в секции данных ус-
танавливается в различные значения. Структуры могут рассматри-
ваться как директивы данных, определяемые пользователем. Имена
элементов структуры преобразуются MASM в побайтовое смещение от-
носительно начала структуры. Теперь ссылаться на числа структуры
Вы можете по имени поля точно так, как это делается в Си или Пас-
кале. Например,
сmр MaxMinShort.ShortInteger1,ax
что эквивалентно
cmp [MaxMinShort + 2],ax
В качестве примера, если мы хотим просмотреть весь список чи-
сел в поисках первого числа с плавающей запятой меньше 0, следует
написать:
mov di,MathList ;получить адрес списка
mov cx,ListLength ;длина списка для зацикл-я
mov bx,(TYPE TrueFalse) ;длина структуры
CmpLup: cmp [di].Float1,0 ;число с ПЗ > 0?
jl ExitLup ;если нет, искать
add di,bx ;указатель на др.структуру
loop Cmplup ;просмотреть весь список эл-ов
ExitLup:...
Адресация к данным во множественных структурах
Одной из удобных возможностей использования структур является
то, что Вы можете в любое время переорганизовывать описание
- 1-58 -
структуры или добавлять к нему новые элементы, которые будут ав-
томатически изменяться при ассемблировании. Например, изменим
предыдущую структуру MathList так, чтобы в ней поменялись местами
двоичные числа с плавающей запятой и добавился элемент
LibraryPtr.
MathNumbers STRUC
Float1 DT (0) ;1 10-байтовое слово
Float2 DT (0) ;1 10-байтовое слово
ShortInteger1 DW (0) ;1 слово
ShortInteger2 DW (0) ;1 слово
LongInteger1 DD (0) ;1 двойное слово
LongInteger2 DD (0) ;1 двойное слово
Boolean1 DB (0) ;1 байт
Boolean2 DB (0) ;1 байт
LibraryPtr DD (?) ;1 двойное слово
MathNumbers ENDS
В нашем случае преимущество использования имен структур в том,
что после реассемблирования всей программы и элементов данных но-
вое описание структуры [di].Float1 по-прежнему будет указывать на
первое число с плавающей запятой, хотя мы и реорганизовали дан-
ные. Таким образом, программный код, который ссылается на данные
по именам структур, не требует корректировки. Заметим, однако,
что если данные файла используют старые описания структур, мы
должны перегруппировать существующие данные так, чтобы они отве-
чали новой структуре. Реорганизация структуры не перегруппировы-
вает существующие данные, для них лишь объявляется относительное
местоположение. Мы должны убедиться, что действительные данные
соответствуют объявлению структуры данных.
В отличие от структур языка Си структуры MASM не могут содер-
жать описания других структур (для этого нет особых причин, и,
вероятно, в более старших версиях MASM это ограничение будет сня-
то). Однако нет причины, чтобы структура не могла содержать адрес
другой структуры, вот почему мы включили в структуру элемент
LibraryPtr. Предположим, что у нас есть структура Library, опре-
деленная так:
Library STRUC
FloatLib DD (0) ;указатель на библ. с ПЗ
ShortIntLib DD (0) ;указатель на библ. с коро-
;ткими целыми
LongIntLib DD (0) ;указатель на библ. с длин-
;ными целыми
ВooleanLib DD (0) ;указатель на библ. с бу-
;левыми значениями
Library ENDS
Теперь мы можем определить набор библиотечных программ с адре-
сами, организованными в структуры. Например,
АddLibs Library
Sublibs Library
MultLibs Library
.
.
.
- 1-59 -
Такая комбинация структур может быть использована как показа-
но в следующем программном сегменте:
lds si,MathList[bx] ;адрес конкретной структуры
push ds ;сохранить адрес структуры данных
push si
lds si,LibraryPtr ;адрес адресов библиотеки
call [ds:si].LongIntLib ;переход на выполнение
;операции
Cоответствующие указатели загружаются в структуру или во время
ассемблирования, или во время выполнения программы. Прелесть ис-
пользования адреса структуры для передачи в подпрограммы парамет-
ров и указателей в том, что вызывающий программный код всегда
один и тот же, несмотря на количество изменений структуры, прово-
димых в течение всей жизни программы. Помещая в структуру указа-
тели на другие структуры данных, мы избавляем программный код от
необходимости знания деталей о данных и/или вызываемых операциях.
Такое "сокрытие данных" развито и более часто используется в объ-
ектно ориентированных языках типа С++ или Smalltalk, однако Вы
можете добиться почти того же самого, применяя только структуры.
Применить определенную Вами структуру можно также и к набору дан-
ных, который создан не Вами. Например, получить доступ к первым
22 байтам PSP (префикс программного сегмента), которые MS-DOS по-
мещает в начало выполняемых файлов, можно через следующую струк-
туру:
PSP STRUC
INT32 DB 2 DUP (?) ; 2 байта
MemSize DW (?) ; 1 слово
Reserved DB (?) ; 1 байт
DOSCall DB 5 DUP (?) ; 5 байтов
TermVctr DW 2 DUP (?) ; 2 слова
BreakVctr DW 2 DUP (?) ; 2 слова
ErrorVctr DW 2 DUP (?) ; 2 слова
PSP ENDS
Получить доступ к PSP можно при помощи следующего программного
фрагмента:
mov di,0 ; PSP начинается со смещения 0
push cs ; сегмент PSP в cs
pop ds ; сегмент PSP -> ds
mov si,[di].MemSize ; размер памяти программы ->
; экстра сегмент
Структуры как параметры подпрограмм
Мы ввели структуры, как способ решения задачи по написанию
универсальной процедуры вызова подпрограмм. Разрешите вернуться к
этой проблеме. Наилучший вариант передачи параметров подпрограмме
- через адрес структуры. В качестве примера передадим данные на-
шей подпрограмме в одном из элементов списка чисел, определенного
при рассмотрении структур. Адреса всегда состоят из сегмента и
смещения. Таким образом, макро, вызывающее подпрограмму и переда-
ющее параметры, будет выглядеть так:
- 1-60 -
@FcnCall MACRO Fnctn,StrucAddr ;адрес подпрограммы и
;структуры
push offset StrucAddr
push segment StrucAddr
call Fnctn
Иногда случается, что Вам необходимо ассемблировать команду,
которую ассемблер не поддерживает. Такое необходимо в ранних вер-
сиях MASM, где из-за ошибки не ассемблируется конкретный тип ко-
манды перехода. Подобная проблема может также возникнуть при ра-
боте на новом процессоре, некоторые команды которого MASM еще не
поддерживает. Один из путей решения этой проблемы заключается в
использовании макро, которое ассемблирует данные, как если бы вы-
полнялась команда из Руководства фирмы Intel. Например, следующее
макро имитирует выполнение команды ближнего перехода.
@JmpShort MACRO destin
db 0EBh ;1-ый байт команды перехода
n = destin - * ;вычислить расстояние перехода
IFE (n LE 255) ;в байт поместится?
db n ;расстояние перехода
ELSE
.ERR ;выдать сообщение об ошибке
%OUT Ошибка в макро @JumpShort.
ENDIF ;конец проверки условия
db 90h ;3-ий байт команды
;короткого перехода
ENDM
Такой пример был выбран для простоты. При создании более слож-
ных команд необходимы и более сложные макросы. Чтобы подбодрить
Вас, сообщим, что некоторые программисты создавали полные кросс-
ассемблеры, используя немногим больше, чем описано в данном ме-
тоде.
Заключение
Наше введение в макросы, условное ассемблирование и структуры
MASM завершено. Мы надеемся, что из примеров данной главы Вы по-
чувствовали довольно сложные, но несомненно стоящие возможности
Макро Ассемблера фирмы Майкрософт.
В этой главе по каждой возможности мы представили множество
примеров, начиная с простых и кончая сложными, так что была опре-
делена мера полезности этих возможностей. Используя примеры и вы-
полняя собственные упражнения, Вы можете определить границу между
возможным и невозможным MASM-ассемблера.
Однако Вам не следует забывать причину использования макросов
и условного ассемблирования. Мы утверждаем, что правильное ис-
пользование этих возможностей может помочь Вам в организации
программы, повышая, следовательно, ее читаемость и надежность и
уменьшая время, необходимое на ее отладку. Мы надеемся, что
представленные примеры вместе с дружескими советами, комментария-
ми и некоторыми предупреждениями дали Вам понимание, как исполь-
зовать эти две возможности для развития Ваших программистских на-
выков.
© KOAP
Open Portal 2000
|
|