|
|
|
|
Макросы данных
Макросы могут использоваться для генерации данных или программного кода. В обоих случаях подход и методы одинаковы, однако, в целях изучения
рассмотрим макросы, генерирующие только данные.
Простейшим примером команды MASM, генерирующей данные, является:
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 ;; конец макро
|
Этот пример значительно сложнее, но он не представляет ничего особенного для опытного программиста. Прежде чем провести построчный анализ этих
директив (термин "директива" мы используем для обозначения того, что является командой для 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 раз
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 ;;конец макро
|
Теперь, когда мы смотрим листинг, мы видим, что каждый байт таблицы чисел 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. Ниже приводится макро создания таких строк:
@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
|
Конечно, для генерации данных при помощи макро можно сделать гораздо больше. Мы лишь подбросили идею некоторых возможностей. Те же методы могут
быть использованы для генерации программного кода, что мы теперь и рассмотрим.
|
|