
             Глава 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,<parameter_list>   НЕОГРAHИЧЕННОЕ ПОВТОРЕНИЕ

                                               Повторяет  блок команд
                                               между IRP  и  ENDM для
                                               каждого значения из para-
                                               meter_list,заменяя фор-
                                               мальный параметр (dummy)на
                                               значение параметра из каж-
                                               дого расширения.

            IRPC      dummy, строка             СИМВОЛ НЕОГРAHИЧЕННОГО
                                                     ПОВТОРЕНИЯ
                                               Повторяет блок команд
                                               между IRPC и  ENDM для
                                               каждого символа строки,
                                               заменяя формальный пара-
                                               метр на символ из каж-
                                               дого расширения.
            ---------------------------------------------------------

                 Таблица 1-2. Специальные символы в макрокомандах
            ---------------------------------------------------------
            Символ                      Описание применения
            ---------------------------------------------------------
            &argument         Соединяет формальные аргументы или сим-
                              волы с текстом. Особенно необходим для
                              подстановки вместо формальных аргументов
                              в строках, заключенных в кавычки.

            ;;comment text    Обозначает комментарий. Такой
                              комментарий никогда не приводится в
                              макроописании.

            !char             Указывает, что следующий далее символ
                              является литералом.   Используется  для
                              включения &, %  и т.д. в макрорасширения,
                              когда эти  символы могут быть интерпре-
                              тированы не как специальные.

            %symbol           Используется для преобразования символа
                              или выражения в число текущей   системы
                              счисления.
            <text>            Угловые скобки (< и >) используются для
                              определения текста,  заключенного между
                              ними, как литерала. Все, что  заключено
                              в такие скобки,  может  быть передано в
                              макро  как одиночный аргумент.
            ----------------------------------------------------------

                                      - 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       <str1>,<str2>     IF строка1 IDENTICAL TO
                                          строка2 (если строка1 иден-
                                          тична строке2)
            IFDIF       <strl>,<str2>     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,<ax,bx,cx,dx,bp,sp,si,di,cs,ds,es,ss>
                     IFIDN   <&&reg>,<&arg>
                     ?isr16  =       TRUE
                     EXITM

                                      - 1-25 -
                     ENDIF
                     ENDM    ;; конец секции IRP
            ;; Если сравнились, остановиться здесь
                     IF      (?isr16)
                     EXITM
                     ENDIF
            ;; Если еще не сравнились, продолжить далее
                     IRP     reg,<ah,bh,ch,dh,al,bl,cl,dl>
                     IFIDN   <&&reg>,<&arg>
                     ?isr8   =        TRUE
                     EXITM
                     ENDIF
                     ENDM    ;; конец секции  IRP
            ;; Если сравнились, остановиться здесь
                     IF      (?isr8)
                     EXITM
                     ENDIF
            ;; Если не сравнились, попробовать прописные имена регистров
                     IRP     reg,<AX,BX,CX,DX,BP,SP,SI,DI,CS,DS,ES,SS>
                     IFIDN   <&&reg>,<&arg>
                     ?sir16  =       TRUE
                     EXITM
                     ENDIF
                     ENDM    ;; конец секции IRP
            ;; Если сравнились, остановиться здесь
                     IF      (?isr16)
                     EXITM
                     ENDIF
            ;; Если еще не сравнились, попробовать еще
                     IRP     reg,<AH,BH,CH,DH,AL,BL,CL,DL>
                     IFIDN   <&&reg>,<&arg>
                     ?isr8   =        TRUE
                     EXITM
                     ENDIF
                     ENDM    ;; конец секции IRP
                     ENDM    ;; конец макроописания
         -----------------------------------------------------------------

            Cердцевиной этого макро, как и любого макро сопоставления, яв-
         ляются команды:

            IRP       reg,<ax,bx,cx,dx,bp,sp,si,di,cs,ds,es,ss>
            IFIDN     <&&reg>,<&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<var> = <begin> to <end> (выполнить
                              для каждого целого значения var между
                              begin и  end  включительно,  увеличивая
                              или уменьшая var при каждом прохождении
                              цикла)
                              END_FOR
            CASE_OF_<var>     CASE <var> OF
                              <case A> (выполнить, если var = A)
                              <case B> (выполнить, если var = B)
                              .  .  .
                              <case N> (выполнить, если 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           <P4>,<+>
                    inc             &p1     ;увеличить счетчик
                    ELSE
                    IFIDN           <p4>,<->
                    dec             &p1     ;уменьшить счетчик
                    ELSE
            ; ОШИБКА - неверная спецификация шага в операторе "@For"

                                      - 1-38 -
                    EXITM
                    ENDIF
                    ENDIF
            first:           ;проверить на необходимость продолжения
            ;; Вставить в программный код проверку условия
                    cmp             &p1,&p3  ;достигнут ли конец?
            ;; Перейти к секции "FOR_TRUE"
                    IFIDN           <p4>,<+>
                    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'>,<subA,subB,subC,subD>
            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,<Options>
                      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,<Options>
                      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>  ;;если аргумент 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> ;;если EKOFLAG = EKO,

                                      - 1-52 -
                                   ;;ассемблировать след-ую строку
                    mov     ah,06h    ;;функция DOS по записи в
                                   ;;стандартный файл
                    ELSE           ;;в противном случае
                    IFIDN <EKOFLAG>,<NOEKO> ;;если 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>  ;;если поле для WAITFLAG не
                                         ;;пусто, ассемблировать
                                         ;;следующее
                    @ChkChr   ;;выявить, ожидается ли символ
                    cmp      al,0   ;; al = 0 => символ не ожидается
                    je       bye    ;;если символа нет, продолжить
                    ENDIF           ;;конец проверки условия
                    IFIDN    <EKOFLAG>,<EKO> ;;если EKOFLAG=EKO, ас-
                                             ;;семблировать
                    MOV      AH,06H          ;;функция DOS по записи
                                             ;;в стандартный вывод
                    ELSE            ;;в противном случае
                      IFIDN <ЕKOFLAG>,<NOEKO> ;;если 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>  ;;если поле для 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>   ;;если EKOFLAG = EKO,

                                      - 1-54 -
                                ;;ассемблировать следующую строку
                    mov     ah,06h  ;;функция DOS по записи в
                                    ;;стандартный вывод
                    ELSE            ;;в противном случае
                      IFIDN <EKOFLAG>,<NOEKO>  ;;если 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,<ParmList2>    ;;неопределен. повторение
                   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,<word1,word2,byte3>
            @FcnCall Fcn2,<word1,byte3>

            Для любого вызова подпрограммы мы можем иметь фактически неог-
         раниченное число параметров.
            В данном макро имеется много недостатков. Одним из этих недос-
         татков является то,  что мы не  покрыли  все  возможные  значения
         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  <FloatAdd,ShortAdd,LongAdd,BooleanAdd>
            Sublibs Library  <FloatSub,ShortSub,LongSub,BooleanSub>
            MultLibs Library <FloatMult,ShortMult,LongMult,BooleanMult>
            .
            .
            .

                                      - 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-ассемблера.
            Однако Вам  не следует забывать причину использования макросов
         и условного ассемблирования.  Мы утверждаем,  что правильное  ис-
         пользование  этих  возможностей  может  помочь  Вам в организации
         программы,  повышая, следовательно, ее читаемость и надежность  и
         уменьшая  время,  необходимое  на  ее отладку.  Мы надеемся,  что
         представленные примеры вместе с дружескими советами, комментария-
         ми и некоторыми предупреждениями дали Вам понимание,  как исполь-
         зовать эти две возможности для развития Ваших программистских на-
         выков.
