|
|
|
|
Метки типа LOCAL
До сих пор макросы, использованные нами, предназначались лишь для генерации простых ассемблерных команд. Теперь предположим, что мы хотим создать
макро, которое выбирает меньшее из двух чисел и помещает результат в какую-то ячейку. Такое макро может выглядеть, например, так:
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
|
Теперь, когда мы снова вызовем макро 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
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-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
в качестве аргумента или макро @InitPrg полагает, что сегмент данных называется dat_seg.
|
|