На главную
Подписка
Новости










Главная / MS-DOS / MS-DOS. РУКОВОДСТВО РАЗРАБОТЧИКА / Глава 1 / Макросы данных Сделать домашней страницей Добавить в избранное Написать писмо

НАЗАД СОДЕРЖАНИЕ ВПЕРЁД

Макросы данных

Макросы могут использоваться для генерации данных или программного кода. В обоих случаях подход и методы одинаковы, однако, в целях изучения рассмотрим макросы, генерирующие только данные.


Простейшим примером команды 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

Конечно, для генерации данных при помощи макро можно сделать гораздо больше. Мы лишь подбросили идею некоторых возможностей. Те же методы могут быть использованы для генерации программного кода, что мы теперь и рассмотрим.


НАЗАД СОДЕРЖАНИЕ ВПЕРЁД

Hosted by uCoz