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,<'1200sq],'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, выполнять для Вас
неинтересную часть работы по программированию. Ниже, в качестве
упрощенного примера того, что мы имели в виду, приведено макро,
реализующее запись символа в файл: