Cамоучитель по Assembler

         

Команда пересылки


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

основываясь на типах
    операндов, которые написал программист; и это одна из причин, по
    которой ассемблер требует для операндов назначения типов, т.е.
    ассемблер должен знать, что представляет собой каждый операнд -
    регистр, байт памяти, слово памяти, сегментный регистр, или
    что=нибудь еще. Такое назначение типов позволяет ассемблеру
    построить правильную машинную команду. В случае использования
    команды MOV ассемблер должен решить, какой из семи вариантов
    является подходящим, основываясь на операндах, написанных
    программистом.
 
      На Фиг.4.1 представлены различные способы, которыми в
    микропроцессоре 8088 можно переслать данные из одного места в
    другое. Каждый прямоугольник означает здесь регистр или ячейку
    памяти. Стрелки показывают пути пересылки данных, которые допускает
    набор команд микропроцессора 8088. Основной путь - из памяти в
    регистры и наоборот. С данными, помещенными в регистры, можно
    работать с большей эффективностью, чем с данными в памяти, так как
    микропроцессор не делает обращения к памяти всякий раз, когда нужны
    данные. Кроме того, все команды микропроцессора 8088 могут указать
    только один операнд памяти. Поэтому, например, команда сложения ADD
    требует, чтобы по крайней мере один из операндов был в регистре.
    Микропроцессор 8088 не имеет возможности сложить одну ячейку памяти
    с другой с помощью одной команды.
 
                   ЪДДДДДДДДДДДДДДДДДДДДї
                   і                і
                   і  Непосредственные  і
                   АДДДДДДВДДДДДДВДДДДДДЩ
      ЪДДДДДДДДДДї            і      і        ЪДДДДДДДДДДДї
      і        і        і      і        і  Регистры і
      і        Г<ДДДДДДДДДДДЩ      АДДДДДДДДДДД>ґ       AX  і
      і        і                        і   BX  і
      і  Память  Г<ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД>ґ     CX  і
      і        і                        і   DX  і
      і        Г<ДДДДДДДДДДДї      ЪДДДДДДДДДДД>ґ       SI  і
      і        і        і      і        і   DI  і
      і        і        і      і        і   BP  і
      і        і        і      і        і   SP  і
      АДДДДДДДДДДЩ            v      v        АДДДДДДДДДДДЩ
                   ЪДДДДДДБДДДДДДБДДДДДДДї
                   і Сегментные регистры і
                   і   CS  DS  ES  SS    і
                   АДДДДДДДДДДДДДДДДДДДДДЩ
 
               Фиг.4.1 Операции пересылки данных
 
      В самой команде MOV может содежаться новое содержимое регистра.
    Такая форма операнда называется непосредственным оперндом; данные
    находятся в самой команде и не требуют вычисления адреса. Вы можете
    рассматривать эту форму адресации как специальный тип, при котором
    операнд находится в самой команде, а не где=то в другом месте
    памяти или в регистре. Кроме команд пересылки, у микропроцессора
    8088 есть и команды обработки данных с непосредственным операндом.
 
      Из Фиг.4.1 также ясно, что команда может переслать
    непосредственнйе данные в регистр или ячейку памяти. Записывать
    информацию в команду бессмысленно, так что поток данных для команды
    с непосредственным операндом имеет одно направление.
 
      Наконец, команда MOV может записать сегментный регистр в память
    или регистр. Она может также загрузить сегментный регистр из памяти
    или из другого регистра. Однако не существует команды загрузки
    сегментного регистра данными с непосредственным операндом; это
    означает, что загружать сегментный регистр такими данными
    непроизводительно. Если в программе необходимо поместить известное
    значение в сегментный регистр, нужно сначала записать это значение
    в один из регистров или в ячейку памяти, а затем можно уже
    пересылать это значение в сегментный регистр. На Фиг. 4.2 показано,
    как это сделать.

           icrosoft (R) Macro Assembler Version 5.00              1/1/80 04:00:28
           Фиг. 4.2  Команда пересылки                      Page   1-1
 
 
                                         PAGE    ,132
                                         TITLE   Фиг. 4.2  Команда пересылки
 
            0000                   CODE    SEGMENT
                                         ASSUME  CS:CODE,DS:CODE
 
            0000                   EXWORD  LABEL   WORD
            0000                   EXBYTE  LABEL   BYTE
 
            0000  8B C3                  MOV     AX,BX           ; Регистр BX --> Регистр AX
            0002  8B D8                  MOV     BX,AX           ; Регистр AX --> Регистр BX
 
            0004  8B 0E 0000 R                 MOV     CX,EXWORD       ; Память --> Регистр
            0008  89 16 0000 R                 MOV     EXWORD,DX       ; Регистр --> Память
 
            000C  8A 2E 0000 R                 MOV     CH,EXBYTE       ; Память --> Регистр (байт)
            0010  88 36 0000 R                 MOV     EXBYTE,DH       ; Регистр --> Память (байт)
 
            0014  BE 03E8                      MOV     SI,1000         ; Непосредственное --> Регистр
            0017  B3 17                  MOV     BL,23           ; Непосредственное --> Регистр (байт)
            0019  C7 06 0000 R 07D0            MOV     EXWORD,2000     ; Непосредственное --> Память
            001F  C6 06 0000 R 2E              MOV     EXBYTE,46       ; Непосредственное --> Память (байт)
 
            0024  A1 0000 R                    MOV     AX,EXWORD       ; Память --> Аккумулятор
            0027  A0 0000 R                    MOV     AL,EXBYTE       ; Память --> Аккумулятор (байт)
            002A  A3 0000 R                    MOV     EXWORD,AX       ; Аккумулятор --> Память
            002D  A2 0000 R                    MOV     EXBYTE,AL       ; Аккумулятор --> Память (байт)
 
            0030  8E 1E 0000 R                 MOV     DS,EXWORD       ; Память --> Сегментный регистр
            0034  8E D8                  MOV     DS,AX           ; Регистр --> Сегментный регистр
            0036  8C 1E 0000 R                 MOV     EXWORD,DS       ; Сегментный регистр --> Память
            003A  8C C0                  MOV     AX,ES           ; Сегментный регистр --> Регистр
 
                                   ;-----  Непосредственное значение в сегментный регистр
 
            003C  B8 ---- R                    MOV     AX,CODE         ; Взять непосредственное значение
            003F  8E D8                  MOV     DS,AX           ; Загрузить его в сегментный регистр
 
            0041                   CODE    ENDS
                                         END
 
                   Фиг. 4.2 Команды пересылки
 
      На Фиг. 4.2 изображен листинг ассемблера некоторых возможных
    вариантов команды MOV. Единственная команда ассемблера MOV
    порождает несколько различных машинных команд.
      Рассматривая Фиг.4.2, обратите внимание на сантаксис команды
    MOV. Команда MOV имеет два операнда: источник и результат. В
    команде они следуют друг за другом, источник следует за
    результатом. Первая команда на рисунке MOV AX, BX пересылает
    содержимое регистра BX в регистр AX. Следующая команда обратна
    предыдущей, содержимое регистра AX пересылается в регистр BX.
    Команда MOV не меняет источник, т.е. команда
 
      MOV AX, BX
 
      меняет регистр AX, результат, но не меняет регистр BX,
    источник.
 
      Никакие из команд MOV не меняют флагов состояния. Хотя иногда
    это кажется неудобным, но является наилучшим способом работы с
    флагами. Как мы увидим далее, микропроцессор 8088 имеет команды,
    которые могут эффективно проверить любую ячейку памяти так, что
    команда пересылки не потребуется. В качестве примера случая, когда
    установка флагов при пересылке не нужна, рассмотрим арифметику
    повышенной точности. Когда программа выполняет вычисления
    повышенной точности, она должна переслать части операндов в
    регистры, чтобы расположить их там для выполнения операции. Такая
    пересылка не меняет ни одного флага, а это позволяет флагам
    обслуживать арифметику повышенной точности.
 
      Как было замечено, существует несколько различных вариантов
    команд пересылки на машинном языке. Объектный код на Фиг. 4.2
    иллюстрирует эти варианты. Если вас интересует структура машинного
    языка, вы можете сравнить объектный код с описанием машинного языка
    в приложении А. Такое сравнение поможет выяснить значение отдельных
    битов в машинном коде. Например, вы сможете увидеть значения данных
    с непосредственным операндом в командах. К счастью, для того, чтобы
    писать программы на ассемблере, вам не требуется точно знать, как
    работает ассемблер.
 
      Если вы хотите достичь наибольшей возможной эффективности
    программ, вам надо изучить объектный код на Фиг. 4.2. Число байтов
    команды непосредственно связано с количеством времени, необходимого
    для выполнения этой команды. Например, команда пересылки, которая
    берет непосредственное значение и посылает его в память, занимает 6
    байт. Набор команд микропроцессора 8088 содержит несколько команд,
    оптимизированных для работы с аккумулятором AX либо AL.
    Использование этих команд поможет вам сэкономить время и место в
    программах, где это важно.
 
      Последние две команды на Фиг. 4.2 показывают, как занести
    непосредственное значение в сегментный регистр. Любой другой
    регистр, в примере это регистр AX, может временно содержать
    непосредственное значение перед его записью в сегментный регистр.
 
      Есть и другеи команды, которые переносят данные. Пример на
    Фиг. 4.3 иллюстрирует эти команды.
          Microsoft (R) Macro Assembler Version 5.00              1/1/80 04:00:33
          Фиг. 4.3 Команды пересылки данных                       Page  1-1
 
 
                                        PAGE    ,132
                                        TITLE   Фиг. 4.3 Команды пересылки данных
 
           0000                   CODE    SEGMENT
                                        ASSUME  CS:CODE, DS:CODE
           0000                   EXDWORD LABEL   DWORD
           0000                   EXWORD  LABEL   WORD
           0000                   EXBYTE  LABEL   BYTE
 
           0000  87 D9                  XCHG    BX,CX     ; Регистр BX <--> Регистр CX
           0002  87 1E 0000 R                 XCHG    BX,EXWORD       ; Регистр BX <--> Память
           0006  93                           XCHG    AX,BX     ; Регистр AX <--> Регистр BX
 
           0007  E4 20                  IN          AL,020H     ; Порт 20H --> AL
           0009  EC                           IN          AL,DX       ; Порт (DX) --> AL
           000A  E6 21                  OUT     021H,AL         ; AL --> Порт 021H
           000C  EE                           OUT     DX,AL     ; AL --> Порт (DX)
 
           000D  8D 36 0000 R                 LEA     SI,EXWORD       ; Адрес(EXWORD) --> SI
           0011  C5 36 0000 R                 LDS     SI,EXDWORD      ; M(EXDWORD) --> SI
                                                          ; M(EXDWORD+2) --> DS
           0015  C4 3E 0000 R                 LES     DI,EXDWORD      ; M(EXDWORD) --> DI
                                                          ; M(EXDWORD+2) --> ES
 
           0019  9F                           LAHF              ; Флаги --> AH
           001A  9E                           SAHF              ; AH --> Флаги
 
           001B  D7                           XLAT    EXBYTE          ; M(BX+AL) --> AL
 
           001C                   CODE    ENDS
                                        END
 
            Фиг. 4.3 Команды пересылки данных

Команда замены


    Команда замены XCHG просто меняет местами содержимое двух ячеек.
    Эта команда может поменять местами содержимое двух регистров, или
    регистра и памяти.  При этом в качестве операндов не могут
    использоваться сегментные регистры.
 
      Команда XCHG заменяет три команды пересылки и не требует
    промежуточной ячейки памяти. Если бы команда замены не
    существовола, программе потребовалось бы три пересылки, чтобы
    обменять значения в регистре AX и в регистре BX. Сначала она должна
    была бы переслать содержимое регистра AX в рабочую ячейку, затем
    переслать содержимое регистра BX в регистр AX, и наконец, переслать
    содержимое рабочей ячейки в регистр BX. Команда XCHG одна выполняет
    эту операцию.

Команды ввода и вывода


    Для выполнения операций ввода и вывода микропроцессор 8088 имеет
    команды IN и OUT соответственно.  Каждое устройство ввода=вывода
    IBM PC имеет один или больше встроенных регистров, с которыми могут
    работать эти команды.  Каждое устройство ввода=вывода имеет адрес
    для каждого регистра в устройстве.    Это адресное пространство
    отличается от адресного пространтва памяти; всего существует 216,
    или 65536 адресов ввода=вывода, доступных микропроцессору 8088.  В
    IBM PC 512 из этих адресов назначены системному каналу ввода=вывода
    и могут использоваться различными адаптерами.  Другие 256 адресов
    исполбзуются на системной плате для управления подключенными туда
    устройствами ввода=вывода.
 
      Команда IN пересылает данные из устройства ввода=вывода в
    регистр AL. Эта команда может указать адрес устройства ввода=вывода
    двумя различными способами. Если адрес устройства находится в
    пределах 0 - 255, он может содержаться в команде как
    непосредственное значение. Если адрес больше 255, команда сообщает
    это косвенно. В случае косвенной команды адрес устройства
    ввода=вывода содержится в регистре DX. Регистр DX может содержать
    адреса всех устройств ввода=вывода, включая те, номера которых
    меньше 256.
 
      Аналогично работает команда OUT, за исключением того, что она
    записывает регистр AL в регистр устройства ввода=вывода. Адреса в
    команде OUT указываются так же, как и в команде IN.
 
      Команды IN и OUT также могут пересылать слова в устройства
    ввода=вывода и из них. В случае работы со словами источником и
    приемником является регистр AX. Так как у микропроцессора 8088
    однобайтовая внешняя шина, устройства ввода=вывода IBM PC работают
    только с байтами при любых операциях ввода=вывода. Это означает,
    что операции ввода=вывода слов не используются в персональной ЭВМ.
    Однако пословные операции ввода=вывода имеют смысл в системе с
    микропроцессором 8086, который имеет тот же набор команд.

Загрузка исполнительного адреса


    Команда загрузки действительного адреса LEA очень похожа на команду
    MOV.  Но вместо пересылки данных из ячейки памяти в регистр команда
    LEA загружает в регистр адрес двнных.  Так как набор команд
    микропроцессора 8088 разрешает иметь в команде только один адрес
    памяти, в качестве приемника результата всегда указывается регистр.
    Команда LEA может ссылаться на операнд источника с помощью любого
    типа адресации, который можно указать байтом mod=r/m.
 
      Во многих случаях команда LEA идентична команде MOV с
    непосредственным операндом. Команды
 
      MOV   BX,             OFFSET            EXWORD
      LEA BX, EXWORD
 
      делают одно и то же. Первая команда - это непосредственная
    пересылка, которая использует смещение переменной EXWORD. Оператор
    OFFSET говорит ассемблеру о том, что в регистр BX надо загрузить
    смещение адресного значения (все адресные значения имеют две части
    - сегмент и смещение) переменной EXWORD. Команда LEA вычисляет
    действительный адрес переменной EXWORD и помещает его в регистр BX.
    В этом случае команды выполняют одинаковые действия.
 
      Но если бы программа загружала в регистр BX адрес десятого
    байта массива, на который указывает регистр DI, команда LEA
    выглядела бы следующим образом
 
      LEA BX, 10[DI]
 
      Микропроцессор выполнил бы вычисление адреса, используя
    информацию из байта mod=r/m в точности, как в случае команды MOV.
    Затем он поместил бы вычисленное смещение, а не данные, по этому
    адресу в регистр BX. Аналогичной команде с непосредственным
    операндом MOV, которая могла бы выполнять ту же функцию, нет. У
    ассемблера здесь нет способа определения непосредственного
    значения, так как адрес неизвестен во время ассемблирования.

Загрузка указателя


    Поскольку механизм адресации микропроцессора 8088 требует
    определения как сегмента, так и смещения каждой переменной,
    желательно загрузить всю эту адресную информацию единственной
    командой.  Эту работу выполняют команды LDS и LES.      Команда
 
      LDS SI, EXDWORD
 
      загружает регистровую пару DS:SI значениями сегмента и
    смещения, содержащимися в переменной EXDWORD. Команда LDS загружает
    в регистр SI значение смещения, расположенное по адресу EXDWORD, а
    в регистр DS - значение сегмента, расположенное по адресу
    EXDWORD+2. Команда LDS одна загружает два 16=битовых регистра
    значением указателя, взятого из некоторой ячейки памяти. Так как
    эта команда устанавливает и сегментный регистр, и регистр смещения,
    программа может сразу адресоваться к объекту, на который этот адрес
    указывает. Программа может организовать указатель из сегмента и
    смещения во время ассемблирования с помощью оператора DD, который
    порождает 32=битовое поле данных. Если операндом DD является
    адресное выражение, двухсловное поле будет содержать сегмент и
    смещение адресного значения в том же самом формате, который
    используется в командах LDS и LES.
 
      Команда LES идентична LDS, за исключением того, что она
    загружает регистр ES. С помощью одной команды записать значения
    сегмента и смещения нельзя. Программа должна записывать значение
    указателя двумя командами пересылки слов, а не одной командой
    записи указателя. Это приемлемо, так как программа обычно читает
    указатель гораздо чаще, чем записывает его. Обычно программа
    записывает указатель один раз, во время инициализации, и может
    быть, иногда меняет его во время смены режимов работы системы. А
    вот читается указатель, вероятно, достаточно часто. В последующих
    главах есть примеры, в которых значения указателей и читаются, и
    записываются.

Пересылка флагов


    Набор команд микропроцессора 8088 имеет команды LAHF и SAHF в
    первую очередь для совместимости с набором команд микропроцессора
    8080.  Команда LAHF берет 8 младших бит регистра флагов - а эти
    флаги совпадают с флагами микропроцессора 8080 - и засылает их в
    регистр AH.  Команда SAHF действует наоборот, младший байт регистра
    флагов загружает из регистра AH.
 
      Вам потребуюся эти две команды, если вы переводите программу из
    системы команд микропроцессора 8080 в команды микропроцессора 8088.
    Они необходимы, чтобы отобразить стековые операции с аккумулятором
    микропроцессора 8080 в стековые операции микропроцессора 8088.

Перекодировка


    Команда перекодировки XLAT преобразует информацию из одного
    представления в другое.  Команда XLAT преобразует значение в
    регистре AL в другое значение, выбираемое из таблицы, на которую
    указывает регистр BX.  На Фиг.4.4 схематически показано, как
    работает эта команда.  Регистр BX вместе с выбранным сегментным
    регистром определяет точку начала таблицы перекодировки в памяти.
    К этому адресу таблицы команда прибавляет содержимое регистра AL,
    значение между 0 и 255.  Данные, расположенные по этому адресу,
    команда XLAT пересылает в регистр AL.  Команда XLAT выполняет
    операцию просмотра таблицы.
 
      Команду XLAT хорошо использовать при кодировании и
    декодировании текстовых данных. С помощью этой команды программа
    может организовать простую замену кодов символов. В следующем
    примере десять символов кода ASCII от 0 до 9 перекодируются в целях
    передачи. Этот метод может использоваться в системе для
    перекодировки информации, передаваемой из одной машины в другую.
    Когда данные принимаются, другая программа возвращает
    закодированные символы к их первоначальному виду. На Фиг. 4.5
    демонстрируется кодировка и декодировка.
 
      На Фиг.4.5 изображены две таблицы перекодировки, одна для
    передачи, а другая для приема. Чтобы передать значение 5, программа
    находит значение 5 в таблице передачи (а), из которой извлекает
    значение 6, которое передает. Когда это значение принимается,
    программа декодирования ищет 6 в таблице приема (b), чтобы
    перекодировать его в истинное значение 5.
 
      На Фиг. 4.6 показана подпрограмма, которая производит это
    декодирование. Подпрограмма перекодирования читает начальное
    значение из порта ввода=вывода, и возвращает кодированное или
    декодированное значение в вызывающую программу через регистр AL.
    Одна и та же программа выполняет как кодирова- ние, так и
    декодирование, меняя таблицы кодировки.
 
      Сначала подпрограмма читает данные из вводного порта 40H в
    регистр AL. Затем она вычитает значение "0" в коде ASCII из
    значения данных, чтобы получить цифровое значение. Это означает,
    что символ "0" дает значение 0 в регистре AL, символ "1" дает 1, и
    т.д. Команда LDS загружает указатель нужной таблицы в пару
    регистров DS:BX. Загружая этот указатель из ячейки памяти - в
            Microsoft (R) Macro Assembler Version 5.00              1/1/80 04:00:38
            Фиг. 4.6 Пример перекодировки                     Page         1-1
 
 
                                          PAGE    ,132
                                          TITLE   Фиг. 4.6 Пример перекодировки
             0000                   CODE    SEGMENT
                                          ASSUME  CS:CODE, DS:CODE
 
                                    ;       Эта программа вводит значение из порта 040H и
                                    ;       декодирует его, используя таблицу перекодировки.
                                    ;       Так как одна и та же программа используется как для
                                    ;       кодировки, так и декодировки, указатель TABLE_POINTER
                                    ;       указывает на соответсвующую таблицу перекодировки.
                                    ;       Вызывая подпрограмму, необходимо установить
                                    ;       этот указатель на соответствующую таблицу.
 
             0000                   TRANSLATE       PROC    NEAR        ; Подпрограмма TRANSLATE
 
             0000  E4 40                        IN      AL,040H               ; Ввод значения из порта
             0002  2C 30                        SUB     AL,"0"                  ; Значение относительно символа "0",
                                                                  ; т.е. относительно начала таблицы
             0004  C5 1E 000A R                 LDS     BX,TABLE_POINTER            ; (DS,BX) указывает на таблицу
             0008  D7                     XLAT    XMIT_TABLE            ; Перекодировка числа
             0009  C3                     RET
 
             000A  000E ---- R            TABLE_POINTER   DD      XMIT_TABLE
             000E  35 37 39 31 33 36 38     XMIT_TABLE      DB      '5791368024'
                 30 32 34
             0018  37 33 38 34 39 30 35     RECV_TABLE      DB      '7384905162'
                 31 36 32
 
             0022                   TRANSLATE       ENDP
             0022                   CODE    ENDS
                                          END
 
            Фиг. 4.6 Пример перекодировки
 
    примере TABLE_POINTER - подпрограмма может использовать любую
    таблицу перекодировки. В этой программе имеются две табдицы, одна
    из них для передачи, названная XMIT_TABLE, которая соответствует
    Фиг.4.5(а), другая - таблица приема, названная RECV_TABLE, -
    соответствует Фиг.4.5(б). Перед вызовом подпрограммы головная
    программа должна записать нужный адресный указатель в переменную
    TABLE_POINTER. Если головная программа принимает коды, она должна
    поместить адрес таблицы RECV_TABLE в переменную TABLE_POINTER.
    Заметим, что эта подпрограмма может проделать любую перекодировку,
    поскольку таблицу перекодировки назначает вызывающая программа.
 
      Команда XLAT выполняет перекодировку по таблице, на которую
    указывает пара регистров DS:BX. В регистре AL находится значение
    между 0 и 9. Команда XLAT складывает это значение с содержимым
    указателя и загружает перекодированное значение в регистр AL.
    Команда RET возвращает управление в вызывающую программу.
      Другим обычным случаем использования команды XLAT является
    смена кода представления символов в одной машине на код
    представления в другой машине. IBM PC, работает в коде ASCII, а
    большинство машин фирмы IBM используют код EBCDIC (Extended
    Binary=Coded=Decimal Interchange Code - расширенный
    двоично=кодированный десятичный код обмена информации). Чтобы
    связываться с такими машинами, в программе надо перекодировать
    символы, и команда XLAT естественным образом подходит для этой
    функции.
 
      Итак, команда XLAT является весьма мощным средством
    перекодировки байтовой или символьной информации. Мощность этой
    команды делает ее редко используемой, так как возможность
    использовать ее преимущества возникает не часто. Однако помните об
    этой команде на тот случай, когда она окажется полностью
    оправданной.

Операции со стеком


    В гл.3 обсуждалось, как реализован стек в микропроцессоре 8088.
    Микропроцессор 8088 адресует стек с помощью регистровой пары SS:SP.
    Помещение объектов в стек приводит к тому, что он растет в сторону
    меньших адресов памяти.  Стек, кроме всего прочего, служит и для
    запоминания адресов возврата из подпрограмм.  В этом разделе
    рассматриваются некоторые команды, которые непосредственно работают
    со стеком.
 
      Фиг.4.7 иллюстрирует ассемблированные стековые команды.
    Мнемоника команд очевидна; за кодами операций PUSH и POP следует
    имя регистра для указания операнда. Единственным исключением
    является помещение и извлечение из стека регистра флагов, которые
    используют мнемонику PUSHF и POPF соответственно. Содержимое любой
    ячейки памяти, которую программа может адресовать, используя
    возможные способы адресации, также может быть помещено или
    извлечено из стека.
 
      При любых действиях со стеком в микропроцессоре 8088 базовой
    единицей информации является 16=битовое слово. Длина любого
    объекта, помещаемого в стек либо извлекаемого из стека, составляет
    одно или несколько слов. Байтовых команд, связанных с засылкой
    данных или извлечением их из стека, не существует. Если, например,
    программе необходимо сохранить содержимое регистра AL а стеке, она
    должна поместить содержимое регистра AX, так как не существует
    способа сохранения только содержимого регистра AL.
 
      Основное назначение стека - временное хранение информации. Как
    мы уже видели, стек используется для сохранения адреса возврата;
    программа также может сохранять данные. Если программа хочет
    использовать регистр, пусть даже сохранить текущие данные, она
    может послать значение этого регистра в стек. Эти данные
    сохраняются в стеке и позже могут быть восстановлены. Например,
           Microsoft (R) Macro Assembler Version 5.00              1/1/80 04:00:43
           Фиг. 4.7 Операции со стеком                      Page   1-1
 
 
                                         PAGE    ,132
                                         TITLE   Фиг. 4.7 Операции со стеком
            0000                   CODE    SEGMENT
                                         ASSUME  CS:CODE,DS:CODE
            0000                   EXWORD  LABEL   WORD
 
            0000  50                           PUSH    AX        ; Поместить регистр в стек
            0001  56                           PUSH    SI
            0002  0E                           PUSH    CS        ; Можно поместить в стек сегментный регистр
            0003  FF 36 0000 R                 PUSH    EXWORD          ; Можно также поместить в стек ячейку памяти
 
            0007  8F 06 0000 R                 POP     EXWORD          ; Можно извлечь то, что в помещено в стек
            000B  07                           POP     ES        ; Можно извлечь в другое место
            000C  5F                           POP     DI
            000D  5B                           POP     BX
 
            000E  9C                           PUSHF             ; Другая мнемоника для флагов
            000F  9D                           POPF
 
                                   ;-----  Пример, демонстрирующий передачу параметров
 
            0010  50                           PUSH    AX
            0011  53                           PUSH    BX
            0012  51                           PUSH    CX
            0013  52                           PUSH    DX
            0014  E8 0017 R                    CALL    SUBROUTINE      ; Передача управления
                                   ;           ...               ; Продолжение программы
 
            0017                   SUBROUTINE      PROC    NEAR
 
            0017  8B EC                  MOV     BP, SP          ; Занесение в BP адреса стека
            0019  8B 46 02                     MOV     AX, [BP+2]      ; Выборка последнего параметра (DX)
            001C  8B 5E 04                     MOV     BX, [BP+4]      ; Выборка третьего параметра (CX)
            001F  8B 4E 06                     MOV     CX, [BP+6]      ; Выборка второго параметра (BX)
            0022  8B 56 08                     MOV     DX, [BP+8]      ; Выборка первого параметра (AX)
                                   ;           ...
            0025  C2 0008                      RET     8               ; Возврат с уничтожением поля параметров
            0028                   SUBROUTINE      ENDP
            0028                   CODE    ENDS
                                         END
 
                 Фиг. 4.7 Операции со стеком
 
    программе нужно ввести код из порта ввода=вывода 3DAH, а в регистре
    DX находятся важные данные. Следующая последовательность команд
 
      PUSH  DX
      MOV DX,                                         3DAH
      IN  AL,                                         DX
      POP DX
 
      сохраняет регистр DX в стеке на то время, пока он нужен в
    программе для выполнения команды IN.
 
      Операции сохранения регистров в стеке обычно используется в
    начале программы. В большинстве случаев подпрограмма старается
    избегать изменения содержимого любого регистра. Поэтому
    подпрограмма, которой нужны регистры для вычислений и для хранения
    адресов, помещает все необходимые ей регистры в стек до выполнения
    команд обработки. Затем, после выполнения, подпрограмма
    восстанавливает регистры из стека с помощью команд POP.
 
      Помните о том, что стек - это структура типа LIFO. Если в вашей
    программе выполняется последовательность команд
 
      PUSH  BX
      PUSH CX
      POP BX
      POP CX
 
      то результирующим эффектом будет обмен значений в регистрах BX
    и CX. Только тот факт, что в команде PUSH был указан регистр BX, не
    означает, что команда POP, указывающая на тот же регистр,
    восстанавливает первоначальное содержимое регистра BX. Еще одним
    важным моментом является то, что команды PUSH и POP должны быть
    сбалансированы, т.е. каждой команде PUSH должна соответствовать
    команда POP. Точно так же, как и в случае скобок в арифметическом
    выражении, если посылки и извлечения из стека не сбалансированы,
    результаты будут неверны. Более того, несбалансированные команды
    PUSH/POP обычно приводят к возврату из подпрограмм по адресу
    значения данных, а не значения указателя команд из=за того, что
    микропроцессор 8088 записывает в стек адрес возврата. Обычно это
    вынуждает микропроцессор выполнять программу, которую программист
    никогда не писал. Поэтому баланс стековых команд обязателен. Будьте
    особенно внимательны в тех случаях, когда в программе есть условный
    переход вокруг стековых операций; можно легко выпустить из виду
    один из вариантов выполнения, что оставит стек несбалансированным.
 
      Наряду с сохранением данных, программа может использовать стек
    в качестве буфера при некоторых пересылках; в частности, не
    существует команды пересылки, которая бы переносила данные из
    одного сегментного регистра в другой. В обычном случае загрузка
    одного сегментного регистра из другого требует сначала загрузки его
    значения а промежуточный регистр. Это достигается следующей
    последовательностью из двух команд:
 
      MOV   AX,CS   ;переслать значение регистра
                    ;CS в регистр AX
      MOV   DS,AX   ;загрузить это значение в
                    ; регистр DS
 
      Каждая из этих команд имеет длину несколько байт, и эта
    последовательность разрушает содержимое регистра AX. Альтернативным
    подходом может быть
 
      PUSH  CS      ; регистр CS поместить в стек
      POP   DS      ; поместить это значение в регистр DS
 
      Результирующий эффект этой последовательности команд тот же,
    регистр DS загружается из регистра CS. Здесь длина программы -
    всего два байта, и к тому же не требуется промежуточный регистр.
    Однако эти две команды занимают больше времени, так как нужны
    дополнительные циклы чтения и записи в стек. Это - метод потери в
    скорости выполнения ради уменьшения размера объектного кода.

Передача параметров


      Стек также служит удобным местом для передачи информации в
    подпрограммы и из них. Обычно программа передает параметры в
    подпрограмму, помещая их в регистры, однако в некоторых случаях
    число параметров превышает размеры регистрового пространства. В
    таких случаях програииа может поместить параметры в стек до
    выполнения команды CALL (вызов подпрограммы). Как мы увидим в
    гл.10, стек является единственным средством передачи параметров в
    подпрограммы, написанные на языке ассемблера, из языков высокого
    уровня Бейсик и Фортран.
 
      Подпрограмма может очень эффективно загружать эти параметры из
    стека. В обычных случаях программа читает информацию из стека
    единственным способом - извлекая ее оттуда. Вместо этого
    подпрограмма может использовать регистр BP, как указатель на
    область стека. Когда программа передает параметры через стек, одной
    из первых команд в подпрограмме выполняется команда
 
      MOV BP, SP
 
      которая загружвет регистр BP текущим значением указателя стека.
    Поскольку регистр BP - адресный регистр, подпрограмма может
    использовать его при адресных вычислениях, а это означает, что все
    параметры доступны как смещения относительно регистра BP.
 
      Конструкторы микропроцессора 8088 определенно помнили об
    описанном выше методе передачи параметров, так как при доступе к
    данным регистр BP использует по умолчанию регистр стекового
    сегмента SS в качестве сегментного регистра. Во всех других
    нормальных случаях доступа к данным микропроцессор использует
    регистр DS. Поскольку стек находится в стековом сегменте,
    регистровую пару SS:BP очень естественно использовать для адресации
    информации в стеке.
 
      На Фиг. 4.7 изображен пример, демонстрирующий использование
    регистра BP для доступа к параметрам, переданным через стек. В этом
    примере головная программа перед выполнением команды CALL поместила
    четыре слова в стек. Подпрограмма загружает в BP указатель данных в
    стеке. Заметим, что смещения, используемые для доступа к данным в
    стеке, учитывают тот факт, что адрес возврата также был записан в
    стек в результате выполнения команды CALL.
 
      В подпрограмме этого примера в вершине стека лежит адрес
    возврата, и регистр BP содержит смещение этой ячейки. Двумя байтами
    ниже в стеке лежит помещенный последним параметр, регистр DX;
    далее, через двухбайтовые интервалы - регистры CX, BX и AX. Таким
    образом, правильным адресом для чтения параметра, содержащегося в
    регистре DX, будет [BP+2], а другие адреса следуют через
    двухбайтовые интервалы. В данном примере значение, находившееся в
    регистре DX, попадает в регистр AX, CX в BX и т.д.
 
      Подпрограмма может использовать регистр BP для адресации стека
    не только при передаче параметров. Подпрограмма может оказаться
    длинной и запутанной настолько, что хранить все необходимые ей во
    время выполнения значения в регистрах трудно. Помещение этих
    значений в стек и загрузка указателя этой области в регистр BP
    решает проблему.
 
      Многим подпрограммам в течение их выполнения также необходима
    локальная память, и подпрограммы могут динамически расположить ее в
    стеке. Всякий раз, когда программа вызывается, она может вычесть
    размер этой области памяти из содержимого указателя стека. Так как
    стек растет по направлению к младшим адресам, вычитание числа из
    регистра SP идентично помещению в стек такого же количества данных
    - за исключением тех данных, которые не инициализированы. После
    этого подпрограмма может использовать регистр BP для адресации
    такой области памяти. Когда наступает момент возврата, подпрограмма
    может прибавить соответствующее значение к указателю стека, и тем
    самым восстановить его прежнее значение. Динамическая организация
    данных означает, что программа использует область памяти только
    тогда, когда она необходима для работы, и не занимает эту память
    все остальное время, поэтому программу можно выполнять на машине с
    малым объемом памяти, что невозможно при другой организации данных.
    Но лучшим является то, что программист не должен создавать сложную
    подсистему управления памятью, так как все находится под
    управлением стековой структуры.
 
      Оператор возврата из подпрограммы на Фиг. 4.7 демонстрирует еще
    одну возможность набора команд микропроцессора 8088. Команда
    возврата из подпрограммы RET может иметь операнд, который
    представляет собой значение, прибавляемое микропроцессором к
    содержимому указателя стека после извлечения адреса возврата. В
    примере используется значение 8; это означает, что восемь байт, или
    четыре слова данных должны быть удалены из стека после возврата.
    Эти значения исчезают навсегда. Результат тот же, какой был бы в
    итоге извлечения значений из стека, чтобы уничтожить их; команда
    возврата уже сделала это автоматически.
 
      Такой метод удаления информации из стека срабатывает только в
    случае параметров, которые вызывающая программа помешает в стек.
    Подпрограмма обязана удалить все динамически распределенные области
    памяти из стека перед выполнением возврата. Она должна сделать это
    явно, а не с помощью команды возврата, так как область данных лежит
    между текущей вершиной стека и адресом возврата.
 
      Подпрограмма может возвратить в стеке некоторую информацию
    вызывающей программе. Если вызывающая программа помешает параметры
    в стек, подпрограмма может изменить их значения и оставить в стеке,
    а вызывающая программа может извлечь их после возврата. Если
    подпрограмма возвращает только один параметр, но вызывалась с тремя
    параметрами в стеке, то выполнить возврат она может с помощью
    команды RET 4. При этом последние два параметра извлекаются из
    стека и только возвращаемый параметр остается в стеке.
      В гл.10, где мы используем подпрограммы на языке ассемблера с
    языками высокого уровня, головная программа помещает параметры в
    стек. Но эти параметры - адреса данных, а не собственно данные. Это
    означает, что ассемблерная подпрограмма не должна возвращать
    параметры в стеке и обязана извлечь все параметры из стека при
    возврате.

Сложение


    Команда ADD выполняет сложение указанных операндов, представленных
    в двоичном дополнительном коде.  Микропроцессор помещает результат
    на место первого операнда после того, как сложит оба операнда.
    Второй операнд не изменяется.  Команда корректирует регистр флагов
    в соответствии с результатом сложения.  Например, команда
 
            ADD   AX,BX
 
    складывает содержимое регистра BX с содержимым регистра AX, и
    оставляет результат в регистре AX.    Регистр флагов сообщает о том,
    был ли результат нулевым, отрицательным, имел ли четность, перенос
    или переполнение.
 
      Фиг. 4.8 кратко иллюстрирует варианты команды ADD.
    Существуют две формы сложения, 8=битовое и 16=битовое. В различных
    формах сложения принимают участие различные регистры. Ассемблер
    следит за тем, чтобы операнды соответствовали друг другу.
    Содержимое байтового регистра (например, CH) не может быть
    прибавлено к ячейке памяти, которая не имеет тип BYTE. Если ячейка
    памяти является одним из операндов, она может быть либо
    операндом=результатом, либо неизменяемым операндом. Тем самым
    команда может прибавить содержимое регистра к ячейке памяти и
    возвратить результат в память. Одним из операндов может также быть
    непосредственное значение. На Фиг. 4.9 показан листинг ассемблера с
    накоторыми арифметическими командами.
 
      Команда сложения с переносом ADC - это та же команда ADD, за
    исключением того, что в сумму включается флаг переноса. Для любой
    формы команды ADD существует сравнимая с ней команда ADC.
     ЪДДДДДДДДї     ЪДДДДДДДДї               ЪДДДДДДДДї
     і      AX   і     і     AX   і               і   AX   і
     і      BX   і     і     BX   і               і   BX   і
     і      CX   і     і     CX   і      ДДДДДДД>   і   CX   і
     і      DX   і     і     DX   і               і   DX   і
     АДДДДДДДДЩ     АДДДДДДДДЩ               АДДДДДДДДЩ
     ЪДДДДДДДДї  +  ЪДДДДДДДДї               ЪДДДДДДДДї
     і      SI   і     і     SI   і               і   SI   і
     і      DI   і     і     DI   і               і   DI   і
     і      BP   і     і     BP   і      ДДДДДДД>   і   BP   і
     і      SP   і     і     SP   і               і   SP   і
     АДДДДДДДДЩ     АДДДДДДДДЩ               АДДДДДДДДЩ
                ЪДДДДДДДДї
                і Память і
                і(слова) і
                АДДДДДДДДЩ
                ЪДДДДДДДДДї
                іНепосред-і
                іственный і
                АДДДДДДДДДЩ
 
     ЪДДДДДДДДДї    ЪДДДДДДДДДї        ЪДДДДДДДДДї
     і      AH    і    і     AH    і        і   AH    і
     і      AL    і    і     AL    і        і   AL    і
     і      BH    і    і     BH    і        і   BH    і
     і      BL    і    і     BL    і        і   BL    і
     і      CH    і    і     CH    і        і   CH    і
     і      CL    і  + і     CL    і        і   CL    і
     і      DH    і    і     DH    і        і   DH    і
     і      DL    і    і     DL    і        і   DL    і
     АДДДДДДДДДЩ    АДДДДДДДДДЩ        АДДДДДДДДДЩ
                ЪДДДДДДДДДї      ЪДДДДДДДДДї
                і Память  і  ДДДДДДД>  і Память  і
                і(слова)  і      і(слова)  і
                АДДДДДДДДДЩ      АДДДДДДДДДЩ
                ЪДДДДДДДДДї
                іНепосред-і
                іственный і
                АДДДДДДДДДЩ
 
                 Фиг. 4.8 Операции сложения
 
      Обе команды сложения, как ADD, так и ADC, устанавливают равным
    1 флаг переноса, если произошел перенос из старшего разряда
    результата. Команда ADD складывает два операнда, не обращая
    внимания на флаг переноса, а команда ADC учитывает и флаг переноса.
    Если флаг переноса равен 0, результат равен результату выполнения
    команды ADD. Если же флаг переноса равен 1, то результат на 1
    больше результата команды ADD. Таким образом, программа может
    использовать флаг переноса для операций повышенной точности.
           Microsoft (R) Macro Assembler Version 5.00              1/1/80 04:00:49
           Фиг. 4.9  Арифметические команды                       Page   1-1
 
 
                                         PAGE    ,132
                                         TITLE   Фиг. 4.9  Арифметические команды
            0000                   CODE    SEGMENT
                                         ASSUME  CS:CODE,DS:CODE
            0000                   EXBYTE  LABEL   BYTE
            0000                   EXWORD  LABEL   WORD
 
            0000  03 1E 0000 R                 ADD     BX,EXWORD       ; BX <- BX + [EXWORD]
            0004  29 0E 0000 R                 SUB     EXWORD,CX       ; [EXWORD] <- [EXWORD] - CX
            0008  12 3E 0000 R                 ADC     BH,EXBYTE       ; BH <- BH + [EXBYTE] + Carry
            000C  18 0E 0000 R                 SBB     EXBYTE,CL       ; [EXBYTE] <- [EXBYTE] - CL - Carry
            0010  F7 1E 0000 R                 NEG     EXWORD          ; [EXWORD] <- -[EXWORD]
            0014  FE 06 0000 R                 INC     EXBYTE          ; [EXBYTE] <- [EXBYTE] + 1
            0018  4E                           DEC     SI        ; SI <- SI - 1
 
            0019  81 C7 00C8                   ADD     DI,200          ; DI <- DI + 200
            001D  83 EC 64                     SUB     SP,100          ; SP <- SP - 100
            0020  83 D1 0A                     ADC     CX,10           ; CX <- CX + 10 + Carry
            0023  83 1E 0000 R 14              SBB     EXWORD,20       ; [EXWORD] <- [EXWORD] - 20 - Carry
 
            0028  3B C3                  CMP     AX,BX           ; Установка флагов по AX - BX
            002A  81 FE 01F4                   CMP     SI,500          ; Установка флагов по SI - 500
 
            002E  F6 26 0000 R                 MUL     EXBYTE          ; AX <- AL * [EXBYTE]
            0032  F7 EB                  IMUL    BX        ; DX:AX <- AX * BX
            0034  F7 36 0000 R                 DIV     EXWORD          ; AX <- DX:AX / [EXWORD]
            0038  F6 FD                  IDIV    CH        ; AL <- AX / CH
 
            003A  27                           DAA               ; Десятичное коррекция для сложения
            003B  2F                           DAS               ; Десятичное коррекция для вычитания
            003C  37                           AAA               ; ASCII коррекция для сложения
            003D  3F                           AAS               ; ASCII коррекция для вычитания
            003E  D4 0A                  AAM               ; ASCII коррекция для умножения
            0040  D5 0A                  AAD               ; ASCII коррекция для деления
            0042  98                           CBW               ; AX <- расширенное по знаку AL
            0043  99                           CWD               ; DX:AX <- расширенное по знаку AX
 
            0044                   CODE    ENDS
                                         END
 
            Фиг. 4.9 Арифметические команды
            Microsoft (R) Macro Assembler Version 5.00              1/1/80 04:00:54
            Фиг. 4.10 Пример вычислений с повышенной точностью      Page         1-1
 
 
                                          PAGE    ,132
                                          TITLE   Фиг. 4.10 Пример вычислений с повышенной точностью
             0000                   CODE    SEGMENT
                                          ASSUME  CS:CODE,DS:CODE
 
             0000  ????????               VALUE1  DD      ?             ; Область данных размером 32 разряда
             0004  ????????               VALUE2  DD      ?
 
                                    ;-----  Сложение двух 32-разрядных чисел
 
             0008  A1 0000 R                    MOV     AX,WORD PTR VALUE1
             000B  01 06 0004 R                 ADD     WORD PTR VALUE2,AX      ; Сложение младших 16 разрядов
             000F  A1 0002 R                    MOV     AX,WORD PTR VALUE1+2
             0012  11 06 0006 R                 ADC     WORD PTR VALUE2+2,AX    ; Сложение старших 16 разрядов
                                    ;-----  Вычитание двух 32-разрядных чисел
 
             0016  A1 0000 R                    MOV     AX,WORD PTR VALUE1
             0019  29 06 0004 R                 SUB     WORD PTR VALUE2,AX      ; Вычитание младшей части
             001D  A1 0002 R                    MOV     AX,WORD PTR VALUE1+2
             0020  19 06 0006 R                 SBB     WORD PTR VALUE2+2,AX    ; Вычитание старшей части
 
             0024                   CODE    ENDS
                                          END
 
          Фиг. 4.10 Пример с повышенной точностью
 
    Фиг. 4.10 иллюстрирует сложение пары 32=битовых чисел; в
    примере складываются 32=битовые числа поля VALUE1 и поля VALUE2, а
    результат помещается в поле VALUE2. Заметим, что один из операндов
    должен быть помещен в регистр. В первом сложении используеся
    команда ADD, так как текущее значение флага переноса несущественно
    для первого сложения. После соответствующего размещения операндов
    программа на Фиг. 4.10 выполняет второе сложение с помощью команды
    ADC, с учетом флага переноса, установленного предыдущим сложением.
    Это также хороший пример показывающий, почему команда MOV не
    устанавливает никаких флагов. Если бы команда MOV изменяла флаги,
    выполнить правильно второе сложение было бы гораздо труднее.

Вычитание


    Команды вычитания SUB и SBB идентичны командам сложения, за
    исключением того, что они выполняют вычитание, а не сложение.  Вы
    можете скорректировать Фиг.4.8 для вычитания, изменив знак "+" на
    знак "-".  Вычитание устанавливает флаги состояния в соответствии с
    результатом операции, причем флаг переноса теперь означает заем.
    Например, команда
 
      SUB AX, BX
 
      вычитает значение регистра BX из значения регистра AX, а затем
    помещает результат в регистр AX. Флаги состояния изменяются так,
    чтобы отражать результат выполнения команды.
      Команда вычитания с заемом SBB решает задачи вычитания
    повышенной точности. Команда SBB учитывает флаг заема при
    вычитании, т.е. значение заема вычитается из результата,
    полученного при нормальном вычитании. На Фиг. 4.10 показано
    вычитание повышенной точности, выполненное с теми же значениями,
    что и сложение. В этом примере значение поля VALUE1 вычитается из
    значения поля VALUE2, помещая результат в поле VALUE2.

Арифметика с одним операндом


    Команда отрицания NEG - это оператор смены знака.  Она меняет знак
    двоичного дополнительного кода операнда=байта или слова.  Другие
    две команды с одним операндом изменяют значение оперенда на 1.
    Команда увеличения INC прибавляет 1 к операнду, а команда
    уменьшения DEC вычитает 1 из операнда.  С помощью команд увеличения
    и уменьшения можно перемещать указатель по массиву ячеек памяти.
    Эти команды также могут реализовать счетчик цикла.      Каждый проход
    по циклу уменьшает счетчик, а когда его значение достигнет 0, цикл
    завершается.
 
      Все эти однооперандные команды могут иметь в качестве операнда
    как байт, так и слово. Если любая из этих команд указывает ячейку
    памяти с помощью одного из косвенных способов адресации, например
    [BX+SI], ассемблер нуждается в помощи, так как ему необходимо знать
    длину операнда в памяти, чтобы породить правильный код операции.
    Команда может использовать модификаторы BYTE PTR или WORD PTR,
    чтобы описать операнд.
 
      Эти три команды влияют на регистр состояния точно так же, как
    это делают арифметические команды. Прибавление 1, вычитание 1 и
    вычитание из 0 идентичны соответственно INC, DEC и NEG; однако
    команды с одним операндом более эффективны.

Сравнение


    Команда сравнения CMP сравнивает два числа, вычитая одно из
    другого.  Она не записывает результат, но флаги состояния
    устанавливает в соответствии с результатом.  Эта команда изменяет
    только флаги.  В программе команда сравнения используется так же,
    как и команда вычитания; однако команды сравнения с заемом не
    существует.
 
      Сравнение с повышенной точностью требует чуть больше усилий,
    чем сравнение байтов или слов. Фактически в этих случаях много
    проще использовать команду вычитания вместо команды сравнения. На
    Фиг. 4.11 показано сравнение пары 32=битовых чисел в памяти с
    использованием регистра AX в качестве области временного хранения.
    Это сравнение определяет, какое из чисел больше. Программа в
    результате своего выполнения устанавливает коды условия. Флаг
    переноса определяет, какое из чисел больше: если флаг равен 1,
    число VALUE больше.
 
      Вторая программа на Фиг. 4.11 проверяет два 32=битовых числа на
    равенство. Программа сохраняет младший результат, а затем
    комбинирует его со старшим, и таким образом выясняет
    эквивалентность результата нулю. Команда OR описана в следующем
    разделе, а здесь существенно то, что она комбинирует два значения
    так, что окончательное значение равно 0 тогда и только тогда, когда
    оба исходных значения равны 0. Результат этой подпрограммы
    сравнения - значение флага нуля; если он равен 1, числа равны.

          Microsoft (R) Macro Assembler Version 5.00              1/1/80 04:00:59
          Фиг. 4.11 Сравнение чисел заданных с повышенной точностью     Page  1-1
 
 
                                        PAGE    ,132
                                        TITLE   Фиг. 4.11 Сравнение чисел заданных с повышенной точностью
           0000                   CODE    SEGMENT
                                        ASSUME  CS:CODE,DS:CODE
 
           0000  ????????               VALUE1  DD        ?     ; Область данных размером 32 разряда
           0004  ????????               VALUE2  DD        ?
 
           0008                   FIG4_11 PROC    NEAR
 
                                  ;-----  Сравниваются по неравенству два 32-разрядных числа
 
           0008                   COMPARE_UNEQUAL:
           0008  A1 0000 R                    MOV     AX, WORD PTR VALUE1
           000B  2B 06 0004 R                 SUB     AX, WORD PTR VALUE2     ; Вычитание младшей части
           000F  A1 0002 R                    MOV     AX, WORD PTR VALUE1+2
           0012  1B 06 0006 R                 SBB     AX, WORD PTR VALUE2+2   ; Вычитание старшей части
           0016  C3                           RET                     ; Возврат с установленными флагами
 
                                  ;-----  Сравниваются по равенству два 32-разрядных числа
 
           0017                   COMPARE_EQUAL:
           0017  A1 0000 R                    MOV     AX, WORD PTR VALUE1
           001A  2B 06 0004 R                 SUB     AX, WORD PTR VALUE2     ; Вычитание младшей части
           001E  8B D8                  MOV     BX, AX                ; В BX младшая часть результата
           0020  A1 0002 R                    MOV     AX, WORD PTR VALUE1+2
           0023  1B 06 0006 R                 SBB     AX, WORD PTR VALUE2+2   ; Вычитание старшей части
           0027  0B C3                  OR          AX, BX            ; Объединение результатов
           0029  C3                           RET                     ; Флаг Z показывает равенство
 
           002A                   FIG4_11 ENDP
 
           002A                   CODE    ENDS
                                        END
 
            Фиг. 4.11 Сравнение с повышенной точностью

Десятичная коррекция


    Те же самые команды, что и для чисел в двоичном дополнительном
    коде, используются в программе для работы с числами в
    двоично=десятичном коде BCD.  Однако результат арифметических
    операций может оказаться неправильным для двоично=десятичного
    представления.  Команды десятичной коррекции корректируют
    результат, полученный после действий двоичной арифметики.
 
      Десятичная коррекция для сложения DAA и десятичная коррекция
    для вычитания DAS используются для работы только с упакованными
    десятичными числами. В упакованном десятичном числе каждый байт
    содержит две десятичные цифры. Команды DAA и DAS работают только с
    байтом данных, содержащимся в регистре AL. В связи с этими
    присущими командам ограничени- ями, ни у DAA, ни у DAS операндов
    нет.
 
      На Фиг. 4.12 показаны два примера. В первом примере
    складываются два упакованных десятичных числа. Оба числа состоят из
    двух десятичных цифр, поэтому они представлены единственными
    байтами. В примере складываются эти числа, оставляя результат в
    регистре AL. Непосредственно за этим следует команда DAA, которая
    корректирует результат сложения, преобразуя его в упакованную
    десятичную форму. После команды DAA в регистре AL остается
    правильное упакованное десятичное число в диапазоне 0 - 99. Если
    результат меньше 100, регистр содержит ответ, а флаг переноса
    содержит 0. Если результат находится в диапазоне 100 - 198, то в
    регистре AL остаются две младшие десятичные цифры, а флаг переноса
    установлен равным 1, показывая, что был перенос.

            Microsoft (R) Macro Assembler Version 5.00              1/1/80 04:01:04
            Фиг. 4.12 Пример двоично-десятичной арифметики          Page         1-1
 
 
                                          PAGE    ,132
                                          TITLE   Фиг. 4.12 Пример двоично-десятичной арифметики
             0000                   CODE    SEGMENT
                                          ASSUME  CS:CODE,DS:CODE
 
             0000  ??               BCD1    DB      ?       ; Две десятичные цифры упакованного числа
             0001  ??               BCD2    DB      ?
 
             0002  ????             BCD1L   DW      ?       ; Четыре десятичные цифры упакованного числа
             0004  ????             BCD2L   DW      ?
 
             0006                   FIG4_12 PROC    NEAR
 
                                    ;-----  Сложение двух упакованных чисел
 
             0006                   DAA_EXAMPLE:
             0006  A0 0000 R                    MOV     AL, BCD1 ; Взять первое упакованное число
             0009  02 06 0001 R                 ADD     AL, BCD2 ; Добавить второе
             000D  27                     DAA          ; Преобразование упакованного числа
             000E  C3                     RET
 
                                    ;-----  Сложение двух упакованных чисел размером по 4 цифры (2 байта)
 
             000F                   DAA_LONG:
             000F  A0 0002 R                    MOV     AL, BYTE PTR BCD1L
             0012  02 06 0004 R                 ADD     AL, BYTE PTR BCD2L      ; Добавление младшей части числа
             0016  27                     DAA                     ; Коррекция упакованного числа
             0017  A2 0004 R                    MOV     BYTE PTR BCD2L, AL      ; Сохранение младшей части
             001A  A0 0003 R                    MOV     AL, BYTE PTR BCD1L+1
 
            Фиг. 4.12 Примеры вычислений с BCD (начало)
             001D  12 06 0005 R                 ADC     AL, BYTE PTR BCD2L+1    ; Добавление старшей части числа
             0021  27                     DAA                     ; Коррекция упакованного числа
             0022  A2 0005 R                    MOV     BYTE PTR BCD2L+1, AL    ; Сохранение старшей части
             0025  C3                     RET
 
                                    ;-----  Сложение двух упакованных чисел
 
             0026                   DAS_EXAMPLE:
             0026  A0 0000 R                    MOV     AL, BCD1
             0029  2A 06 0001 R                 SUB     AL, BCD2              ; Вычитание значений
             002D  2F                     DAS                     ; Коррекция упакованного числа
             002E  C3                     RET
 
             002F                   FIG4_12 ENDP
 
             002F                   CODE    ENDS
                                          END
 
            Фиг. 4.12 Примеры вычислений с BCD (продоложение)
 
      Команда DAA правильно устанавливает регистр флагов. Если в
    результате сложения получилось значение в диапазоне 100 - 198, флаг
    переноса показывает перенос из старшей десятичной позиции.
    Аналогично, нулевой результат оставляет установленным в 1 флаг
    нуля. В случае операций с упакованными десятичными числами флаги
    знака и переполнения не имеют значения, хотя флаг знака
    устанавливается, если установлен старший бит регистра AL. Команда
    DAA использует флаг дополнительного переноса для определения вида
    коррекции, но после выполнения этой команды флаг дополнительного
    переноса неопределен.
 
      Второй пример на Фиг. 4.12 демонстрирует десятичное сложение
    повышенной точности. Оно весьма похоже на двоичную арифметику с
    повышенной точностью, за исключением того, что после сложения
    каждого байта появляется команда DAA. Из=за ограничений, присущих
    команде DAA, в примере нельзя было сложить два упакованных
    десятичных слова, как слова, а затем применить коррекцию. С
    упакованными десятичными числами разрешена только байтовая
    арифметика.
 
      Наконец, на Фиг. 4.12 показано, как использовать команду DAS.
    Это делается так же, как и при сложении, но команда DAS должна
    следовать за вычитанием. Здесь тоже допустимы только байтовые
    операции.

Симовльная коррекция: сложение и вычитание


    Команды символьной коррекции очень похожи на команды десятичной
    коррекции.    Они следуют за сложением или вычитанием распакованных
    десятичных чисел.  В тех же случаях, в которых программа использует
    команды десятичной коррекции DAA и DAS для упакованных десятичных
    чисел, она использует символьную коррекцию для распакованных
    десятичных чисел.  В распакованных десятичных числах цифры от 0 до
    9 представляются одним байтом.  Такая конструкция числа называется
    символьной десятичной из=за того, что такие числа просто
    преобразовывать в символьный вид и наоборот (прибавлять и вычитать
    30H, соответственно).
 
      После сложения двух распакованных десятичных чисел программа
    обычно выполняет команду символьной коррекции при сложении AAA,
    которая преобразует результат в правильное распакованное
    представление десятичного числа. Правила сложения идентичны
    правилам для упакованных десятичних чисел. Поскольку сложение двух
    распакованных десятичных чисел может дать в результате число,
    большее 9, командам AAA и AAS требуется для работы не только
    регистр AL. В случае команды AAA младшая цифра скорректированного
    результата остается в регистре AL. Если десятичное сложение привело
    к переносу из младшей цифры, команда AAA устанавливает равными 1
    флаги переноса и дополнительного переноса. В других случаях она
    сбрасывает их в 0. Содержимое других флагов не определено после
    команды коррекции. Команды символьной коррекции отличаются от
    десятичных команд тем, что они влияют на содержимое регистра AH, а
    также устанавливают флаг переноса, если есть перенос из младшей
    значащей цифры.
 
      Символьная коррекция вычитания AAS используется в программе
    после вычитания одного распакованного десятичного числа из другого,
    и результат этой байтовой операции должен быть помещен в регистр
    AL. Результат команды символьной коррекции остается в регистре AL,
    и если вычитание привело к появлению заема, команда AAS уменьшает
    регистр AH, а также устанавливает флаги переноса и дополнительного
    переноса. В противном случае флаги сбрасываются. Другие флаги после
    команды не определены.

Умножение


    Микропроцессор 8088 значительно мощнее предшествовавших ему
    8=битовых устройств.  Одна из причин увеличения мощности -
    добавление команд умножения и деления к набору команд
    микропроцессора.  В прежних микропроцессорах выполнение операций
    умножения и деления требовало вызова подпрограмм на языке
    ассемблера.
 
      Существует две команды умножения. Покоманде MUL умножаются два
    целых числа без знака и дает результат без знака. По команде IMUL
    умножаются целые числа со знаком. При умножении целых чисел в
    качестве операндов используются числа, представленные в
    дополнительном коде и получается результат, имеющий правильный знак
    и значение.
 
      Обе команды умножения работают как с байтами, так и со словами.
    Однако диапазон форм представления операндов гораздо уже, чем для
    команд сложения и вычитания. Фиг. 4.13 иллюстрирует варианты
    команды умножения. Чтобы умножить 8 бит на 8 бит, один из операндов
    должен быть в регистре AL, а результат всегда оказывается в
    регистре AX. Результат может иметь длину вплоть до 16 бит
    (максимальное получаемое значение равно 255 * 255 = 65025). Чтобы
    умножить 16 бит на 16 бит, один из операндов нужно поместить в
    регистр AX. Результат, который может быть длиной до 32 бит
    (максимальное значение 65535 * 65535 < 2+32) помещается в пару
    регистров; в регистре DX содержатся старшие 16 бит результата, а
    врегистре AX - младшие 16 бит. Умножение не допускает
    непосредственного операнда.
 
      Установка флагов командой умножения несколько отличается от
    других арифметических команд. Единственные имеющие смысл два флага
    - это флаги переноса и переполнения, и они по=разному
    устанавливаются двумя командами.
 
      Команда умножения без знака MUL устанавливает оба флага, если
    старшая половина резул в регистре AL получится 2AH; команда AAM
    преобразует этот результат, оставляя в регистре AH число 04H, и
    02H в регистре AL - или распакованное десятичное число 42 в паре
    регистров AH:AL.
 
                ЪДДДДДДДДї
                і AX   і
                і BX   і
                і CX   і
                і DX   і
      ЪДДДДДДДДї    АДДДДДДДДЩ            ЪДДДДДДДДВДДДДДДДДї
      і   AX   і *  ЪДДДДДДДДї      ДДДДД>      і   DX      і   AX   і
      АДДДДДДДДЩ    і   SI   і            АДДДДДДДДБДДДДДДДДЩ
                і DI   і
                і BP   і
                і SP   і
                АДДДДДДДДЩ
                ЪДДДДДДДДї
                і Память і
                і(слова) і
                АДДДДДДДДЩ
                   (a) Умножение слов
 
                ЪДДДДДДДДДї
                і AH    і
                і AL    і
                і BH    і
                і BL    і
                і CH    і
                і CL    і
                і DH    і
                і DL    і
      ЪДДДДДДДДї    АДДДДДДДДДЩ      ЪДДДДДДДДВДДДДДДДДї
      і   AL   і *  ЪДДДДДДДДДї  ДДДДД>  і   AH   і   AL   і
      АДДДДДДДДЩ    і Память  і      АДДДДДДДДБДДДДДДДДЩ
                і(байты)  і
                АДДДДДДДДДЩ
                    (b) умножение байтов
 
            Фиг. 4.13 Операции умножения
      Целое умножение со знаком (IMUL) устанавливает флаги переноса и
    переполнения в соответствии с тем же критерием, т.е. эти флаги
    устанавливаются в случае, когда результат не может быть представлен
    тоько своей младшей половиной. Однако, поскольку число имеет знак,
    то задача не сводится только к сравнению старшей половины
    результата с нулем. Команда IMUL устанавливает флаги, если старшая
    половина результата не является распространением знака младшей. Это
    значит, что в случае положительного результата проверка будет такой
    же, как для команды MUL - установка флага происходит при ненулевой
    старшей половине результата (но самый старший бит равен нулю,
    указывая на положительность результата). В случае отрицательного
    результата IMUL устанавливает флаги, если старшая половина
    результата состоит не только из едениц (но старший бит равен 1,
    указывая на отрицательность результата). Например, перемножение
    байтов с отрицательным результатом устанавливает флаги когда
    результат менше -128 - наименьшего числа, представимого в одном
    байте. Другой пример, перемножение слов с положительным
    результатом, устанавливает флаги, если результат превышает 32 767 -
    наиболшее представимое одним словом число.

Символьная коррекция: умножение


    Когда в программе перемножаются два неупакованных десятичных числа,
    результат в регистре AL является двоичным числом. Поскольку
    наибольшее неупакованное десятичное в двоичном представлении число
    равно 9, то максимальный результат при BCD-умножении без упаковки
    равен 81. Однако, этот результат не является значимым неупакованным
    BCD-представлением этого числа. Команда символьной коррекции для
    умножения (AAM - от ASCII Adjust for Multiply) переводит такой
    двоичный результат в неупакованный десятичный. Командой AAM старшая
    десятичная цифра результата помещается в регистр AH, а в AL
    остается младшая десятичная цифра. Например, если программа
    перемножает значения 6 и 7 и результат в AL равен 2AH, то команда
    AAM преобразует результат, помещая в AH 04H, а в AL - 02H, что
    соответсвует неупакованному десятичному числу 42 в регистрах AH:AL.
 
      Команда AAM вычисляет распакованный десятичный результат с
    помощью деления числа в регистре AL на 10. Она помещает частное в
    регистр AH, оставляя остаток в регистре AL. Команда AAM
    устанавливает флаги нуля и знака в соответствии с результатом в
    регистре AL. Так как результат - распакованное десятичное число,
    знак всегда положителен, а знак нуля устанавливается равным 1,
    только если исходное число кратно 10 - т.е. если младшая значащая
    десятичная цифра равна 0. Остальные флаги после команды AAM
    остаются неопределенными. Флаг переноса теперь не имеет смысла,
    потому что умножение двух распакованных десятичных чисел никогда не
    дает результата, превосходящего число, представимое двумя
    десятичными цифрами.
 
      Программа также всегда может использовать команду AAM для
    деления двоичного числа в регистре AL на 10. В таком виде она может
    рассматриваться, как специальный случай команды деления, которая
    делит однобайтовое число в регистре AL на 10. Частное помещается в
    регистр AH, остаток - в регистр AL.

Команда деления


    Одна из арифметических операций микропроцессора 8088 - деление.
    Как и в случае умножения, существует две формы деления - одна для
    двоичных чисел без знака DIV, а вторая для чисел в дополнительном
    коде IDIV.    Любая форма деления может работать с байтами и словами.
 
      Команда деления DIV выполняет деление без знака и дает как
    частное, так и остаток. Как и в случае умножения, операнды должны
    находиться на специфических местах. Также подобно умножению, для
    деления одно из этих чисел в два раза длиннее обычного операнда:
    делимое является операндом двойной длины. Байтовые команды делят
    16=битовое делимое на 8=битовый делитель. В результате деления
    получается два числа. Деление помещает частное в регистр AL, а
    остаток в регистр AH. Такое расположение операндов делает команду
    деления дополнительной к команде умножения; это означает, что
    умножение регистра AL на байтовый операнд, а затем деление регистра
    AX на тот же операнд возвращает регистр AL к его первоначальному
    состоянию. Регистр AH будет содержать 0, поскольку остатка нет.
    Фиг. 4.14 схематически иллюстрирует команду деления.
                      ЪДДДДДДДДї
                      і AX   і
                      і BX   і
                      і CX   і
                      і DX   і            Остаток
     ЪДДДДДДДДВДДДДДДДДї    АДДДДДДДДЩ          ЪДДДДДДДДВДДДДДДДДї
     і      DX   і   AX   і *  ЪДДДДДДДДї      ДДДДД>      і   DX      і   AX   і
     АДДДДДДДДБДДДДДДДДЩ    і SI   і            АДДДДДДДДБДДДДДДДДЩ
                      і DI   і
                      і BP   і
                      і SP   і
                      АДДДДДДДДЩ
                      ЪДДДДДДДДї
                      і Память і
                      і(слова) і
                      АДДДДДДДДЩ
                      (a) Деление слов
 
                      ЪДДДДДДДДДї
                      і AH    і
                      і AL    і
                      і BH    і
                      і BL    і
                      і CH    і
                      і CL    і
                      і DH    і
                      і DL    і       Остаток
            ЪДДДДДДДДї    АДДДДДДДДДЩ      ЪДДДДДДДДВДДДДДДДДї
            і   AL   і *  ЪДДДДДДДДДї  ДДДДД>  і   AH   і   AL   і
            АДДДДДДДДЩ    і Память  і      АДДДДДДДДБДДДДДДДДЩ
                      і(байты)  і
                      АДДДДДДДДДЩ
                       (b) Деление байтов
 
                   Фиг.4.14 Операции деления
      Команда, работающая со словами, делит 32=битовое делимое на
    16=битовый делитель. Делимое находится в паре регистров DX:AX,
    причем регистр DX содержит старшую значащую часть, а регистр AX -
    младшую. Деление слов помещает частное в регистр AX, а остаток в
    регистр DX. Здесь опять=таки умножение и деление взаимно
    дополнительны: умножение регистра AX на слово, а затем деление его
    на то же слово возвращает регистр AX к его первоначальному
    состоянию. Теперь регистр DX становится нулевым, так как остатка
    нет.
 
      Ни один из флагов состояния не определен после команды деления.
    Однако во время деления может возникнуть ошибка значимости. Если
    частное больше, чем может быть помещено в регистр результата,
    микропроцессор не может дать правильный результат. В случае деления
    байтов частное должно быть меньше 256, и меньше 65535 в случае
    операции со словами. Микропроцессор не устанавливает никаких флагов
    для сигнализации при этой ошибке, вместо этого он выполняет
    программное прерывание уровня 0. Как и в случае других программных
    прерываний, это прерывание по делению на 0 сохраняет флаги, регистр
    кодового сегмента и указатель команды в стеке. Затем микропроцессор
    передает управление в ячейку, на которую ссылается указатель по
    адресу 0. Подпрограмма деления на 0 должна предпринять
    соответствующие действия по обработке этой ошибки. (Прерывание 0
    называется делением на 0 даже тогда, когда это прерывание возбудило
    деление на число, отличное от нуля. В документации фирмы Intel это
    прерывание называется делением на нуль, хотя более точно его надо
    было бы назвать прерыванием по переполнению после деления).
 
      Деление целых чисел со знаком IDIV отличается от команды DIV
    только тем, что оно учитывает знаки обоих операндов. Если результат
    положителен, все происходит так же, как было описано для команды
    DIV, за исключением того, что максимальное значение частного
    соответственно равно 127 и 32767 для байтов и слов. Если результат
    отрицателен, частное усекается, а остаток имеет тот же знак, что и
    делимое. Минимальные значения частных для отрицательных результатов
    -128 и -32768 для байтов и слов.
 
      Делимое (AX)   Делитель(MOD-R/M)    Частное (AL)     Остаток(AH)
    ------------------------------------------------------------------
         7             2                 3                1
         7            -2                -3                1
        -7             2                -3               -1
        -7            -2                 3               -1
    ------------------------------------------------------------------
 
            Фиг. 4.15 Примеры деления со знаком
 
      На Фиг.  4.15 показаны четыре примера деления, а также
    полученные в них результаты.  Все примеры, приведенные здесь,
    байтовые, т.е.  делимое находится в регистре AX, а делитель
    указывается байтом mod=r/m.  Деление помещает частное в регистр AL,
    а остаток в регистр AH.  Заметим, что знак остатка всегда тот же,
    что и у делимого.  Значение частного всегда усекается в направлении
    нуля.

Символьная коррекция: деление


    Так же, как и другие арифметические операции, деление имеет
    соответствующую команду для обслуживания распакованных десятичных
    чисел.  Однако в отличие от других команд, програииа должна
    выполнять команду символьной коррекции деления AAD до выполнения
    команды деления.  Команда AAD берет две цифры распакованного
    десятичного числа из регистра AX (старшая значащая цифра адресуется
    в регистре AH) и преобразует его в двоичное число в регистре AL,
    оставляя в регистре AH нуль.  После этого в регистре AX оказывается
    значение, готовое для деления на десятичное распакованное число,
    состоящее из одной цифры.  Команда AAD устанавливает коды условия в
    соответствии с результатом в регистре AL.  Флаги нечетности, знака
    и нуля соответствуют значению AL, а остальные неизвестны.
 
      Есть случаи, когда после деления может оказаться, что частное -
    это не одна десятичная цифра. Так получается потому, что в этом
    случае переполнение после деления не регистрируется. В худшем
    случае 99 делится на 1, давая частное 99, число, меньшее
    максимального как для команды DIV, так и для команды IDIV, так что
    переполнение не возникает. Однако это число больше максимального
    распакованного десятичного числа из одной цифры, которое равно 9.
    Существует два метода борьбы с таким случаем. Во=первых, после
    каждой последовательности команд AAD=DIV можно проверять, не
    превысило ли частное 9, и вызывать соответствующую обработку
    переполнения. Или программа может использовать команду AAM после
    деления, чтобы преобразовать частное в распакованное десятичное
    число из двух цифр. Но в этом случае программа должна где=либо
    сохранить остаток до выполнения команды AAM, так как она разрушит
    содержимое регистра AH. Этот способ порождает десятичный результат,
    состоящий из двух цифр, после деления значения из двух цифр на
    число из одной цифры. Но если распакованный десятичный делитель
    нулевой, то деление вызовет прерывание по делению на нуль,
    показывая, что произошло переполнение при делении.

Команда преобразования


    Когда программа выполняет целое деление со знаком, возникает
    проблема, если делимое - байтовый операнд.  Иногда нужно разделить
    байтовое значение на байтовое, но команда деления требует, чтобы
    делимое занимало регистр AX.  В случае деления со знаком
    необходимо, чтобы значение в регистре AX было правильной копией
    числа, представленного в дополнительном коде.  Команда
    преобразования байта в слово CBW решает эту задачу; она берет число
    из регистра AL и расширяет его знак в регистр AH.  Таким образом,
    если значение в регистре AL положительно, команда заполняет регистр
    AH нулями, если же значение в регистре AL отрицательно, она
    устанавливает в регистре AH все единицы.  Команда CBW загружает в
    регистр AX 16=битовое число, равное значению исходного байта в
    регистре AL.  В случае деления слов команда преобразования слова в
    двойное слово CWD выполняет идентичную функцию.  Команда CWD
    расширяет знак слова из регистра AX в регистр DX.  Эти две команды
    расширяют операнды до выполнения целого деления со знаком.
 
      В случае целого деления без знака при тех же условиях знака уже
   не существует, и его не надо расширять в старшую часть делимого. В
    этом случае правильным является заполнение регистра AH (или
    регистра DX) нулями перед делением. Существует много команд,
    которые могут выполнить эту задачу, включая команду MOV с
    непосредственным операндом, или просто
 
      SUB   AH,AH
 
      что гарантирует обнуление регистра AH.

Арифметический пример


    Чтобы проиллюстрировать функции, которые мы рассмотрели в
    предыдущих разделах, давайте решим арифиетическую задачу на языке
    ассемблера.  Пример прост, но использует многие команды.  Задача
    заключается в вычислении частного двух арифметических выражений, в
    которых некоторые числа постоянны, а другие переменны.  Все числа
    являются 16=битовыми целыми числами со знаком.
 
      Формула вычислений:
                        A * 2 + B * C
                  X = ------------------
                          D - 3
 
      Эта задача решается подпрограммой на языке ассемблера,
    изображенной на Фиг. 4.16. Подпрограмма сначала выполняет два
    умножения. Так как микропроцессор 8088 всегда помещает результат
    16=битового умножения в пару регистров DX:AX, в примере результат
    первого умножения переносится в пару регистров BX:CX перед
    выполнением второго умножения. Когда оба умножения завершены,
    программа выполняет сложение числителя. Поскольку умножение дает
    32=битовые результаты, в программе требуется сложение повышенной
    точности. Это сложение оставляет результат в DX:AX. В примере
    знаменатель вычисляется в регистре CX, а затем на него делится
    числитель. Программа записывает частное из регистра AX в переменную
    результата X. Остаток в этой задаче игнорируется.

           Microsoft (R) Macro Assembler Version 5.00              1/1/80 04:01:10
           Фиг. 4.16 Пример арифметических вычислений              Page   1-1
 
 
                                         PAGE    ,132
                                         TITLE   Фиг. 4.16 Пример арифметических вычислений
 
                                   ;-------------------------------------------------------------
                                   ; Производятся вычисления по формуле
                                   ;
                                   ;              A * 2   +   B * C
                                   ;        X  =      -------------------
                                   ;                   D  -  3
;
                                   ; Все переменные - 16-разрядные целые числа со знаком
                                   ;-------------------------------------------------------------
 
                              Фиг. 4.16 Арифметический пример (начало)
            0000                   CODE    SEGMENT
                                         ASSUME  CS:CODE,DS:CODE
 
            0000  ????             X           DW      ?               ; Память для переменных
            0002  ????             A           DW      ?
            0004  ????             B           DW      ?
            0006  ????             C           DW      ?
            0008  ????             D           DW      ?
 
            000A                   FIG4_16 PROC    NEAR
 
            000A  B8 0002                      MOV     AX, 2           ; Загрузка константы
            000D  F7 2E 0002 R                 IMUL    A               ; DX:AX = A * 2
            0011  8B DA                  MOV     BX, DX
            0013  8B C8                  MOV     CX, AX          ; BX:CX = A * 2
            0015  A1 0004 R                   MOV     AX, B
            0018  F7 2E 0006 R                 IMUL    C               ; DX:AX = B * C
            001C  03 C1                  ADD     AX, CX
            001E  13 D3                  ADC     DX, BX          ; DX:AX = A * 2  +  B * C
            0020  8B 0E 0008 R                 MOV     CX, D
            0024  83 E9 03                     SUB     CX, 3           ; CX = D - 3
            0027  F7 F9                  IDIV    CX        ; AX = (A*2 + B*C) / (D-3)
            0029  A3 0000 R                    MOV     X, AX           ; Сохранение результата
            002C  C3                           RET
 
            002D                   FIG4_16 ENDP
            002D                   CODE    ENDS
                                         END
 
            Фиг. 4.16 Арифметический пример (продолжение)

Логические операции


    Следующий класс команд - логические команды.  Эти команды, точно
    так же, как и арифметические команды, преобразуют данные, но делают
    это не арифметически.  В то время как команды сложения и вычитания
    связаны со школьной арифметикой, логические команды работают со
    значениями 0 и 1, которые использует ЭВМ.  В общем случае, эти
    команды позволяют программе выполнять битовые операции.
 
      Четырьмя основными логическими командами являются AND (и), OR
    (или), XOR (исключающее или), NOT (не). Существуют и другие
    логические функции, состоящие из этих четырех функций, но в
    микропроцессоре 8088 для них нет соответствующих команд. Эти четыре
    команды работают непосредственно с нулями и единицами двоичного
    кода.
 
      Простейшая функция выполняется командой NOT. Эта команда
    основывается на определении единицы и нуля, как истины (TRUE) и лжи
    (FALSE) соответственно. Предложение NOT TRUE (не истина) - это
    FALSE (ложь), а предложение NOT FALSE (не ложь) - это TRUE
    (истина). Команда NOT инвертирует все биты числа данных. Иначе
    говоря, команда NOT эквивалентна вычитанию данных из величины,
    состоящей из всех единиц. Фиг. 4.17 показывает, как оператор NOT
    действует на единственный бит.
 
         Значение    NOT(Значение)
       -------------------------------------
             0          1
             1          0
       ------------------------------------- Фиг. 4.17 Операция NOT
 
      Остальные три логические функции имеют два операнда.  На
    Фиг.4.18 показаны результаты действий, произведенных каждой
    функцией над парой бит.
 
        X       Y     X AND Y    X OR Y     X XOR Y
       -----------------------------------------------------
        0       0        0      0        0
        0       1        0      1        1
        1       0        0      1        1
        1       1        1      1        0
       -----------------------------------------------------
 
                Фиг. 4.18 Логические операции
 
      Поскольку микропроцессор 8088 работает с байтами или словами,
    он повторяет результаты таблицы с каждым битом операнда.  Например,
    байтовая команда выполняет логическое И со значениями нулевого бита
    обеих операндов а помещает результат в бит 0 результата.  Затем эта
    команда повторяет функцию И с битами от первого до седьмого.  В
    результате получается побитовая функция И над отдельными битами
    операндов.
 
      Функция AND равна 1 только тогда, когда оба операнда равны 1. В
    терминах истинности, результат есть истина только тогда, когда и X,
    и Y истинны. Функция OR дает 1, если хотя бы один из операндов
    равен 1. Результат есть истина, если либо X, либо Y являются
    истинными. Результат функции XOR равен 1, только если один из
    операндов равен 1, а другой равен 0. Если же оба операнда равны 0,
    или оба равны 1, то результат равен 0. Функция исключающее ИЛИ в
    точности соответствует сложению, у которого игнорируется перенос.
 
      Фиг. 4.19 иллюстрирует логические команды микропроцессора
    8088. Команде NOT требуется один операнд, а ее форма идентична
    команде NEG. Остальные логические команды копируют синтаксис команд
    сложения и вычитания.
 
      Когда микропроцессор 8088 делает логическую операцию, он
    устанавливает флаги в соответствии с результатом. Так как операция
    не арифметическая, флаги переноса и переполнения всегда
    устанавливаются равными 0. Флаг дополнительного переноса после
    логических операций остается неопределенным, в то время как другие
    флаги (знак, нуль) правильно отражают результат операции.
    Исключение представляет команда NOT, которая не изменяет ни одного
    флага.
          Microsoft (R) Macro Assembler Version 5.00              1/1/80 04:01:15
          Фиг. 4.19 Логические команды                      Page  1-1
 
                                        PAGE    ,132
                                        TITLE   Фиг. 4.19 Логические команды
           0000                   CODE    SEGMENT
                                        ASSUME  CS:CODE,DS:CODE
           0000                   EXBYTE  LABEL   BYTE
           0000                   EXWORD  LABEL   WORD
 
           0000  22 06 0000 R                 AND     AL, EXBYTE            ; AL <- AL and [EXBYTE]
           0004  81 E3 9FEF                   AND     BX, 1001111111101111B   ; BX <- BX and 9FEFH
           0008  80 26 0000 R 03              AND     EXBYTE, 00000011B     ; [EXBYTE] <- [EXBYTE] and 3
 
           000D  08 2E 0000 R                 OR          EXBYTE, CH          ; [EXBYTE] <- [EXBYTE] or CH
           0011  0B 16 0000 R                 OR          DX, EXWORD          ; DX <- DX or [EXWORD]
           0015  0D FFF9                      OR          AX, 0FFF9H          ; AX <- AX or 0FFF9H
 
           0018  33 1E 0000 R                 XOR     BX, EXWORD        ; BX <- BX xor [EXWORD]
           001C  30 1E 0000 R                 XOR     EXBYTE, BL        ; [EXBYTE] <- [EXBYTE] xor BL
           0020  34 EF                  XOR     AL, 0EFH          ; AL <- AL xor 0EFH
 
           0022  F7 D1                  NOT     CX                ; CX <- not CX
           0024  F6 16 0000 R                 NOT     EXBYTE            ; [EXBYTE] <- not [EXBYTE]
 
           0028  F7 06 0000 R 0003            TEST    EXWORD, 0003H     ; Установка флагов по [EXWORD] and 3
           002E  84 E0                  TEST    AH, AL            ; Установка флагов по (AH and AL)
           0030  A9 0002                      TEST    AX, 02H           ; Установка флагов по (AX and 2)
 
           0033  D1 C1                  ROL     CX, 1       ; Циклический сдвиг влево на 1
           0035  D3 0E 0000 R                 ROR     EXWORD, CL        ; Циклический сдвиг вправо на CL
 
           0039  D0 16 0000 R                 RCL     EXBYTE, 1         ; Циклический сдвиг с переносом
                                                            ; влево на 1
           003D  D3 DB                  RCR     BX, CL            ; Циклический сдвиг с переносом
                                                            ; вправо на CL
           003F  D1 E0                  SHL     AX, 1       ; Сдвиг логический влево на 1
           0041  D1 E0                  SAL     AX, 1       ; Сдвиг арифметический влево на 1
 
           0043  D3 EB                  SHR     BX, CL            ; Сдвиг логический вправо на CL
           0045  D0 3E 0000 R                 SAR     EXBYTE, 1         ; Сдвиг арифметический вправо на 1
 
           0049                   CODE    ENDS
                                        END
 
                  Фиг. 4.19 Логические команды
 
      Первоочередное назначение логических операций в микропроцессоре
    8088 - работа с битами. Самой малой единицей данных, с которой
    может работать этот микропроцессор, является байт. Ни одна из
    арифметических команд не может непосредственно выделить или
    изменить единственный бит, а логические команды позволяют программе
    обрабатывать отдельные биты.
 
      Почему интересны однобитовые операции? Во многих случаях
    программа должна хранить значение индикатора - истина - ложь. Этот
    бит может означать, что печатающее устройство занято, что нажата
    регистровая клавиша, или что инициализация программы выполнена. В
    таких случаях расточительно отводить байт для хранения
    единственного бита информации. Программа может объединить несколько
    таких битов в одном байте, если у нее есть способ выделения
    отдельных битов для их проверки и установки. Такое объединение
    однобитовых флагов очень широко используется в устройствах
    ввода=вывода, которые имеют различные адреса. Устройству
    ввода=вывода гораздо проще работать с разными битами по одному
    адресу, чем распознавать многие адреса.
 
      Логические команды могут выделить отдельные биты в байте или
    слове так, что они могут быть установлены, сброшены, проверены. Для
    выделения битов эти команды используют маску. Значение маски
    используется командой побитно. Чтобы установить какой=либо один
    бит, нужно использовать команду OR. В этом случае все значения
    маски - нули, кроме единицы на месте устанавливаемого бита. Команда
    OR над маской и другим операндом устанавливает 1 в выбранном бите,
    а другие биты результата оставляют неизменными. Аналогично,
    оператор AND может сбросить единственный бит. В маске все разряды
    единичные, кроме сбрасываемого бита. Этот бит сбросится в 0, а
    остальные останутся без изменений.
 
      Программисты не используют функцию исключающее или столь же
    часто, как команды AND и OR, но она тоже бывает полезна. Команда
    может выполнить взаимное дополнение одного бита с данными. Запишите
    маску для команды XOR так, чтобы на месте инвертируемого бита была
    1, а на всех других местах 0. Когда команда XOR выполнится, биты,
    соответствовавшие нулям, останутся без изменений, а биты,
    соответствовавшие единицам маски, инвертируются. Если начальное
    значение бита было 0, 1 XOR 0 дает 1, дополнение к 0, а если
    начальное значение было 1, 1 XOR 1 дает 0, дополнение к 1.
 
      Последняя логическая команда - TEST (проверка). Эта команда
    идентична команде AND, за исключением того, что она не записывает
    результат, но устанавливает флаги в соответствии с ним, т.е.
    команда TEST соответствует команде AND, как команда CMP
    соответствует команде SUB. Эта команда проверяет заданный бит, или
    набор битов внутри байта или слова.
 
      Как работает команда проверки? Предположим, программа хочет
    проверить младший значащий бит байта, бит 0. Программа порождает
    маску 01H либо в регистре, либо как непосредственное значение.
    Команда TEST (или AND) дает результат с гарантированными нулями по
    всем позициям, за исключением бита 0; значение бита 0 отражает
    значение оригинала. Если нулевой бит оригинала содержит 0, то бит
    остается нулевым. Если он сначала единичен, результат ненулевой, и
    флаг нуля сбрасывается; если же бит 0, результат нулевой, и флаг
    нуля устанавливается. Таким образом, программа может проверить
    единственный бит, выполняя команды TEST и AND с маской, которая
    имеет единственную единицу на месте проверяемого бита; регистр
    флагов отразит состояние этого единственного бита. Команда TEST
    проверяет заданный бит без разрушения других битов, поскольку эта
    команда не изменяет поле результата.

Операции сдвига и поворота


    Остальные логические команды на Фиг. 4.19 выполняют сдвиги данных.
    Команда сдвига перемещает все биты в поле данных либо вправо, либо
    влево.  Это можно проиллюстрировать церковной скамьей, на которой
    сидят мужчины и женщины.  Каждый раз, когда приходит новый человек
    и садится на край скамьи, остальные сидящие на ней сдвигаются на
    одно место. Если скамья уже заполнена, то крайний в результате
    такого сдвига вытесняется с нее. Команда сдвига делает в точности
    то же самое, только вместо женщин и мужчин здесь выступают нули и
    еденицы.
 
      На Фиг.4.20 показаны восемь различных команд сдвига; у этих
    команд имеются некоторые вариации.    Сначала мы рассмотрим общие для
    этих команд черты.
 
      Как и другие логические команды, сдвиги работают с байтами и
    словами. Каждая команда указывает единственный операнд. Этот
    операнд может быть либо регистром, либо ячейкой памяти. Все эти
    команды используют байт mod=r/m для описания операнда.
 
            ЪДДДДДДДДДДДДДї          ЪДДДДДДДДДДДДДДДї
      ЪДДДДї  і ЪДДДДДДДДДї і        і  ЪДДДДДДДДДДї і  ЪДДДДї
      і CY Г<ДБДґ ДАННЫЕ  Г<Щ        АД>ґ  ДАННЫЕ  ГДБД>ґ CY і
      АДДДДЩ      АДДДДДДДДДЩ         АДДДДДДДДДДЩ      АДДДДЩ
             ROL                      ROR
 
   ЪДДДДДДДДДДДДДДДДДДДДДДДДДДї      ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДї
   і  ЪДДДДї      ЪДДДДДДДДДї   і      і    ЪДДДДДДДДДДї      ЪДДДДї і
   АДДґ CY Г<ДДДґ ДАННЫЕ  Г<ДДЩ      АДДД>ґ  ДАННЫЕ  ГДДД>ґ CY ГДЩ
      АДДДДЩ      АДДДДДДДДДЩ         АДДДДДДДДДДЩ      АДДДДЩ
             RCL                        RCR
 
 
      ЪДДДДї      ЪДДДДДДДДДї         ЪДДДДДДДДДДї      ЪДДДДї
      і CY Г<ДДДґ ДАННЫЕ  Г<ДДД 0   0 ДДД>ґ  ДАННЫЕ  ГДДД>ґ CY і
      АДДДДЩ      АДДДДДДДДДЩ         АДДДДДДДДДДЩ      АДДДДЩ
             SHL                      SHR
                              ЪДДДДДДДї
                              і       і
      ЪДДДДї      ЪДДДДДДДДДї       і   ЪДДДБДДДДДДї    ЪДДДДї
      і CY Г<ДДДґ ДАННЫЕ  Г<ДДД 0     АДД>ґ  ДАННЫЕ  ГДДД>ґ CY і
      АДДДДЩ      АДДДДДДДДДЩ         АДДДДДДДДДДЩ      АДДДДЩ
             SAL                      SAR
 
            Фиг. 4.20 Операции сдвига.
 
      Во всех командах сдвига определяется счетчик сдвигов, т.е.
    программа указывает число битов, на которое надо сделать сдвиг; это
    число и есть счетчик сдвигов. Его наиболее распространенное
    значение - единица. Такой счетчик сдвигает биты операнда на одну
    позицию. Однако команда может задать произвольный счетчик сдвигов,
    занося его значение в регистр CL пред сдвигом. Если в команде
    указано, что счетчик сдвигов надо взять из регистра CL, значение
    этого регистра определяет число сдвигов битов. Число в регистре CL
    может быть любым от 0 до 255, но его практически имеющие смысл
    значения лежат в пределах 0 - 16. Значение 0 не вызывает сдвига, а
    любое значение больше 16 сдвигает битов больше, чем содержит
    операнд.
 
      Другая общая черта команд сдвига - это установка флага
    переноса. Бит, попадающий за пределы операнда, имеет специальное
    место. Команды сдвига помещают последний выдвинутый из операнда бит
    в флаг переноса. Если сдвиг был на один бит, то бит из дальнего
    конца операнда становится новым значением флага переноса. В случае
    многобитового сдвига, вдвигаемый в перенос бит появляется изнутри
    операнда. Флаг переноса имеет значение для операций повышенной
    точности. Поскольку операнд операции сдвига может иметь максимум 16
    бит, программа может организовать работу с данными большего размера
    с помощью нескольких сдвигов и флага переноса. Программа
    "разрезает" операнд на 16=битовые куски, а затем сдвигает каждую
    часть на один бит каждый раз. Флаг переноса используется программой
    для передачи выдвинутой информации в следующую часть сдвигаемого
    операнда.
 
      Верхние четыре команды на Фиг.4.20 - команды циклического
    сдвига. На рисунке схематически представлена работа каждой команды.
    Циклические сдвиги переносят появляющийся в конце операнда бит в
    другой конец. Циклический сдвиг влево ROL и циклический сдвиг
    вправо ROR различаются лишь направлением сдвига данных. Аналогично,
    циклический сдвиг влево с переносом RCL и циклический сдвиг вправо
    с переносом RCR являются зеркальным отражением друг друга. Команды
    ROL и RCL различаются в трактовке флага переноса. Байтовая команда
    RCL рассматривает данные как 9=битовые, причем роль девятого бита
    играет флаг переноса. Если операнд - слово, команда ROL циклически
    сдвигает 16 бит, а команда RCL циклически сдвигает 17 бит.
 
      Команды снизу Фиг.4.20 не возвращают выдвигаемые из операнда
    биты в свой операнд. Эти биты попадают в флаг переноса, а затем
    просто исчезают. Значение, вдвигаемое в операнд, определяется типом
    сдвига. В случае логического сдвига вдвигаемый бит всегда 0;
    арифметический сдвиг выбирает вдвигаемый бит таким, чтобы сохранить
    знак операнда.
 
      Почему сдвиг называется арифметическим, если он входит в группу
    логических команд? Сдвиг числа на одну позицию (бит) эквивалентен
    умножению или делению этого числа на 2. В десятичной системе
    счисления, добавление нуля в конце числа умножает его на 10. В
    двоичной арифметике добавление 0 в конце умножает число на 2. Так
    как ЭВМ не может добавить другой бит в конце операнда, операция
    сдвига действует аналогично. Команда сдвига влево перемещает все
    биты влево на одну позицию, а в младшую позицию помещает 0. Таким
    образом, сдвиг влево умножает число на 2. Если величина сдвига
    больше единицы, число умножается на 2, возведенное в степень,
    равную содержимому счетчика сдвигов. Например, сдвиг влево на 3
    бита эквивалентен умножению на 8.
 
      Сдвиг числа вправо - это то же самое деление на 2. Сдвинутый
    операнд - частное, а флаг переноса - остаток. Если счетчик сдвигов
    больше 1, операнд по=прежнему есть частное, а остаток теряется.
    Таким образом, команды сдвига делают эффективным умножение и
    деление на степень 2. Фактически, воэможность замены умножения
    сдвигом становится хорошим выходом в ситуациях, когда необходимо
    исключить умножение, даже если множитель не есть степень 2.
 
      При арифметическом сдвиге вместо деления на 2 отрицательного
    числа возникает следующая проблема. Если команда вдвигает 0 в
    старший бит, результат становится положительным. Команда
    арифметического сдвига вправо SAR решает эту проблему путем
    восстановления значения старшего бита во время сдвига. Поэтому
    отрицательное число остается отрицательным, а положительное -
    положительным. Эта проблема не возникает в случае сдвига влево,
    поскольку бит знака находится у операнда слева. Из=за этого команды
    логического сдвига влево SHL и арифметического сдвига влево SAL
    идентичны.
 
      В связи с арифметической природой, все команды сдвогов влияют
    на флаг переполнения так же, как и на флаг переноса. Флаг
    переполнения не определен в случае счетчиков сдвига больших
    единицы, но при единичных сдвигах команды устанавливают флаг
    переполнения только в случае, если в результате операции изменился
    знак числа. Если старший бит не изменился, флаг переполнения
    сбрасывается, т.е. флаг переполнения показывает, дает ли
    подразумеваемое сдвигом умножение или деление правильный результат
    в дополнительном коде.
 
      На Фиг. 4.21 приведены два примера команд сдвига. Первый пример
    демонстрирует умножение на число с помощью команд сдвига влево. В
    примере выполняется умножение на 9, не являющееся степенью 2.
    Сначала в примере данные сдвигаются влево на три позиции, чтобы
    умножить число на 8. Затем программа складывает полученное значение
    с первоначальным, давая результат, равный первоначальному числу,
    умноженному на 9.
 
      Недостатки этого метода очевидны. Он требует много больше
    команд, чем простое умножение - которое выглядело бы примерно так:
 
      PUSH    DX
      MOV     DX,9
      IMUL    DX
      POP     DX
 
      Кроме того, умножение на 9 с помощью сдвига дает 16=битовый
    результат, а не 32=битовый, как команда IMUL.
 
      Все же в программе умножение с помощью сдвига может оказаться
    желательным в некоторых случаях. В первую очередь, его преимущество
    - скорость выполнения. Команда IMUL требует много времени, тогда
    как команда сдвига выполняется гораздо быстрее. В случае примера на
    Фиг. 4.21, метод сдвига работает примерно на 25% быстрее. Выигрыш
    небольшой, но может оказаться решающим для приложения, зависящего
    от умножения целых чисел на 9. Умножения на степень 2 могут дать и
    больший выигрыш в скорости выполнения.
          Microsoft (R) Macro Assembler Version 5.00              1/1/80 04:01:21
          Фиг. 4.21 Примеры инструкций сдвига               Page  1-1
 
                                        PAGE    ,132
                                        TITLE   Фиг. 4.21 Примеры инструкций сдвига
           0000                   CODE    SEGMENT
                                        ASSUME  CS:CODE,DS:CODE
 
                                  ;--------------------------------------------------
                                  ; Эта программа умножает число, заданное в регистре AX
                                  ; на 9 без использования команды умножения
                                  ;--------------------------------------------------
 
           0000                   MUL9    PROC    NEAR
 
           0000  51                           PUSH    CX              ; Сохранение регистра CX в стеке
           0001  50                           PUSH    AX              ; Временное сохранение AX
           0002  B1 03                  MOV     CL, 3     ; Будем сдвигать регистр AX на 3 разряда,
           0004  D3 F8                  SAR     AX, CL          ;  тем самым уножая на 8
           0006  8B C8                  MOV     CX, AX          ; CX <- AX * 8
           0008  58                           POP     AX              ; Восстановление AX
           0009  03 C1                  ADD     AX, CX          ; AX <- исходное значение * 9
           000B  59                           POP     CX              ;
           000C  C3                           RET
           000D                   MUL9    ENDP
 
                                  ;--------------------------------------------------
                                  ; Эта программа программа выделяет один бит в
                                  ; регистре AX, номер которого задан в регистре CL
                                  ;--------------------------------------------------
 
           000D  53                           PUSH    BX              ; Сохранение регистра BX в стеке
           000E  BB 0001                      MOV     BX, 1     ; Создание маски (1 в разряде 0 регистра BX)
           0011  D3 C3                  ROL     BX, CL          ; Сдвиг маски
           0013  23 C3                  AND     AX, BX          ; Выделение требуемого разряда
           0015  5B                           POP     BX              ; Восстановление регистра BX
           0016  C3                           RET
 
           0017                   CODE    ENDS
                                        END
 
            Фиг. 4.21 Примеры сдвига
 
      Второй пример на Фиг. 4.21 показывает, как использовать сдвиг
    на переменное число разрядов для выборки отдельного бита. Этот
    фрагмент предполагает, что исходная информация находится в регистре
    AX, а регистр CL содержит номер бита, выбираемого из регистра AX:
    если содержимое регистра CL равно 8, из регистра AX выбирается бит
    8. Программа сдвигает маску в регистре BX на указанную в регистре
    CL позицию, а команда AND изолирует выбранный бит.
 
      Для того чтобы этот пример работал правильно, число в регистре
    CL должно быть в диапазоне 0 - 15. Можно было бы использовать
    команду AND, чтобы выделить младшие четыре бита значения сдвига в
    регистре CL; команда AND CL, 0FH гарантирует, что число в регистре
    CL находится в пределах 0 - 15. Вы можете изменить этот пример так,
    чтобы выделить более одного бита из слова. Можно было бы выделить
    тетраду из 16=битового слова, заменив значение маски в регистре BX.

Команды обработки строк


    Одной из функций, в которой в наборе команд микропроцессора 8088
    уделено особое внимание, является обработка строк.      Строка символов
    или чисел, с которыми программа работает, как с группой, является
    обычным типом данных.  Программа пересылает строку из одного места
    в другое, сравнивает ее с другими строками, а также ищет в ней
    заданное значение.  Обычным типом данных является строка символов.
    Программа представляет каждое слово, предложение либо другую
    структуру строкой символов в памяти.  Функции редактирования,
    например, в большой степени используют операции поиска и пересылки.
    Строковые команды микропроцессора 8088 выполняют эти операции с
    минимальными программными затратами, а также при минимальном
    времени исполнения.
 
      Сначала давайте обсудим принципы работы со строками. Программа
    может выполнять строковые операции как над байтами, так и над
    словами; отдельные элементы строк могут иметь 8 либо 16 бит.
    Строковые команды не используют способы адресации, используемые
    остальными командами обработки. Фактически строковые команды очень
    конкретны в адресации и не допускают каких=либо вариаций. Строковые
    команды адресуют операнды комбинациями регистров DS:SI либо ES:DI.
    Операнды источника используют регистровую пару DS:SI, а операнды
    результата регистровую пару ES:DI, откуда и названия
    индекс=регистров источника и результата. Все строковые команды
    имеют встроенную коррекцию адреса после выполнения операции. Строка
    состоит из многих элементов, но строковые команды обработки строк
    могут работать только с одним элементом в каждый момент времени,
    поэтому программа тоже работает со строкой по одному элементу в
    момент времени. Автоматическое увеличение или уменьшение адреса
    дает возможность быстрой обработки строковых данных. Флаг
    направления в регистре состояния управляет направлением обработки.
    Когда он установлен равным 1, адрес уменьшается, если флаг сброшен
    в 0, то увеличивается. Размер операнда определяет количество
    увеличений=уменьшений. Байтовые команды обработки строк изменяют
    адрес на 1 после каждой операции, а команды обработки строк над
    словами изменяют адрес на 2. Тем самым после выполнения операции
    указатель ссылается на следующий элемент строки.

Загрузка и запись


    Листинг ассемблера на Фиг. 4.22 показывает различные строковые
    команды.  Загрузка строки LODS и запись строки STOS являются
    простейшими строковыми командами.  Если программа указывает
    байтовый операнд в команде LODS, то она загружает в регистр AL
    байт, на который указывает пара регистров DS:SI.  Затем она
    изменяет регистр SI на единицу; он увеличивается, либо уменьшается,
    в зависимости от состояния флага направления.  Если команда LODS
    указывает на слово, то она загружает регистр AX и изменяет регистр
    SI на 2.  Команда STOS строго противоположна, и записывает байт из
    регистра AL либо слово из регистра AX в ячейку памяти.  В случае
    записи ячейка определяется парой регистров ES:DI.  Команда записи
    изменяет регистр DI либо на единицу, либо на 2, в зависимости от
    типа операнда.
 
      Программист может писать на ассемблере команду LODS (а также и
    все другие строковые команды) различными способами. Тип операнда
    можно указать частью кода операции, либо ассемдлер может определить
    тип элемента строки, основываясь на операнде, присутствующем в
    команде. Как показано на Фиг. 4.22, команда
 
      LODS EXBYTE
 
      порождает команду загрузки строк байтов, так же как и команда
      LODSB.

           Microsoft (R) Macro Assembler Version 5.00              1/1/80 04:01:26
           Фиг. 4.22 Команды обработки строк                      Page   1-1
 
                                         PAGE    ,132
                                         TITLE   Фиг. 4.22 Команды обработки строк
            0000                   CODE    SEGMENT
                                         ASSUME  CS:CODE,DS:CODE,ES:CODE
            0000                   EXBYTE  LABEL   BYTE
            0000                   EXWORD  LABEL   WORD
            0000                   EXBYTE1 LABEL   BYTE
            0000                   EXWORD1 LABEL   WORD
 
            0000  AC                           LODS    EXBYTE          ; Загрузка AL из DS:SI
            0001  AD                           LODS    EXWORD          ; Загрузка AX из DS:SI
            0002  AC                           LODSB             ; Загрузка AL из DS:SI
            0003  AA                           STOS    EXBYTE          ; Сохранение AL в ES:DI
            0004  AB                           STOS    EXWORD          ; Сохранение AX в ES:DI
            0005  AB                           STOSW             ; Сохранение AX в ES:DI
            0006  F3/ AA                       REP     STOSB           ; Сохранение AL в ES:DI в цикле CX раз
 
            0008  A4                           MOVS    EXBYTE1, EXBYTE ; Пересылка байта [ES:DI] <- [DS:SI]
            0009  A5                           MOVS    EXWORD1, EXWORD ; Пересылка слова [ES:DI] <- [DS:SI]
            000A  A4                           MOVSB             ; Пересылка байта [ES:DI] <- [DS:SI]
 
            000B  F3/ A5                       REP     MOVSW           ; Пересылка CX слов [ES:DI] <- [DS:SI]
 
            000D  AE                           SCAS    EXBYTE1         ; Сравнение AL с [ES:DI]
            000E  F3/ AE                       REPE    SCASB           ; Сравнение AL с [ES:DI] пока равно
            0010  F2/ AF                       REPNE   SCASW           ; Сравнение AX с [ES:DI] пока не равно
 
            0012  A7                           CMPS    EXWORD, EXWORD1 ; Сравнение слова [DS:SI] с [ES:DI]
            0013  F3/ A7                       REPE    CMPSW           ; Сравнение слов [DS:SI] с [ES:DI] пока
                                                           ;      равно в цикле CX раз
            0015  F2/ A6                       REPNE   CMPSB           ; Сравнение байт [DS:SI] с [ES:DI] пока
                                                           ;      не равно в цикле CX раз
            0017                   CODE    ENDS
                                         END
 
            Фиг.4.22 Строковые команды
      В первом случае ассемблер определяет, что строка состоит из
    байта, поскольку EXBYTE - переменная типа BYTE. Во втором случае
    программист непосредственно указывает, что работает с байтами.
    Собственно ассемблер не требует поля операнда. Программисты чаще
    используют вторую форму, так как не имеют имени переменной,
    связанной со строкой. Программа динамически располагает строку в
    памяти, для нее не существует фиксированного места, и,
    следовательно, нет и имени переменной. Команда STOS аналогична.
    Чтобы непосредственно указать строку слов, а не байтов,
    используются коды операций LODSW и STOSW. Ассемблер должен знать,
    для байтовой строки или строки слов написана команда, поскольку
    машинные команды различны для различных типов строк. Эта разница
    определяет значение, на которое надо изменить индексный регистр.
 
      Операнд в команде должен быть указан в том случае, если в
    программе используются основные формы команд LODS и STOS. Если
    программа не имеет удобной метки для строки, она может использовать
    формы LODSB и STOSB. Преимущество использования основной формы LODS
    и указания операнда заключается в том, что ассемблер при этом
    проверяет не только тип операнда, но и возможность его адресации.
    Так как команда LODS работает с объектами только в сегменте DS,
    оператор ASSUME должен соответственно описывать расположение
    сегмента поименованной переменной. Аналогично, ассемблер проверяет
    основную форму команды STOS на адресацию сегмента ES. Любая форма
    приемлема для ассемблера, но лучше использовать основную форму,
    чтобы позволить ассемблеру наилучшим способом проверить наличие
    ошибок в программе до выполнения.

Префикс REP


    Существует специальный случай использования строковых команд.  Есть
    префикс, специально предназначенный для строковых команд.  Также
    как префикс подавления сегментации, используемый для порождения
    специальной сегментной адресации, он предшествует обычной команде и
    модифицирует ее работу.  А именно, этот префикс вводит строковую
    команду в цикл.  Мнемоника префикса REP происходит от английского
    слова Repeat - повторить.  Микропроцессор 8088 использует этот
    префикс в тесной связи с регистром CX, который указывает число
    повторений команды.
 
      Примером является команда STOSB. Команда
 
      REP STOSB
 
      есть специальная форма команды записи байта. Эта команда
    повторяется до тех пор, пока содержимое регистра CX не уменьшится
    до 0. Команда STOSB записывает байт из регистра AL в ячейку памяти,
    которая указывается парой регистров ES:DI, а затем увеличивает или
    уменьшает регистр DI на единицу так же, как и обычная команда
    STOSB. Затем префикс REP уменьшает регистр CX, и если он теперь не
    нуль, повторяет всю команду целиком. Запись строки повторяется до
    тех пор, пока регистр CX не достигнет нуля.
 
      Такая возможность превращает команду STOS в команду заполнения.
    Программа помещает заполнитель в регистр AL, счетчик байта в
    регистр CX, адрес блока в пару регистров ES:DI и сбрасывает флаг
    направления. Затем команда REP STOSB заполняет блок памяти
    значением из регистра AL. Такой фрагмент кода показан на Фиг. 4.23.

          Microsoft (R) Macro Assembler Version 5.00              1/1/80 04:01:31
          Фиг. 4.23 Заполнение области памяти               Page  1-1
 
 
                                        PAGE    ,132
                                        TITLE   Фиг. 4.23 Заполнение области памяти
           0000                   CODE    SEGMENT
                                        ASSUME  CS:CODE,DS:CODE,ES:CODE
 
                                  ;--------------------------------------
                                  ; В этом примере область данных BYTE_BLOCK
                                  ; заполняется значением 01H
                                  ;--------------------------------------
 
           0000  8D 3E 000C R                 LEA     DI, BYTE_BLOCK        ; DI <- адрес области данных
           0004  B9 0032 90                   MOV     CX, BYTE_BLOCK_LENGTH   ; CX <- размер заполняемой области
           0008  B0 01                  MOV     AL, 01H               ; Символ для заполнения
           000A  F3/ AA           REP     STOS    BYTE_BLOCK                ; Заполнение
 
           000C  0032[            BYTE_BLOCK        DB          50 DUP(?)
                  ??
                             ]
 
           = 0032                 BYTE_BLOCK_LENGTH       EQU     $-BYTE_BLOCK
 
           003E                   CODE    ENDS
                                        END
 
            Фиг. 4.23 Заполнение блока
 
      В случае команды LODS префикс REP не имеет смысла. Загрузка
    непрерывной строки данных в аккумулятор не дает программе
    возможности иметь дело с данными по мере их поступления. Однако
    префикс REP весьма полезен для работы с другими командами обработки
    строк.

Пересылка строки


    Может показаться удобным использовать команды LODS и STOS для
    пересылки данных из одного места в другое, но для этой цели
    существует другая команда, пересылка строки MOVS.  Эта команда
    подобна комбинации команд LODS и STOS.  Она берет данные из пары
    регистров [DS:SI], помещает их в пару регистров [ES:DI], и изменяет
    как регистр SI, так и регистр DI, чтобы они указывали на следующую
    ячейку в каждой строке.  Команда MOVS делает это одна, и не
    загружает аккумулятор во время пересылки.  Команда MOVS делает
    сочетание LODS и STOS более быстрым и дающим меньше побочных
    эффектов.
 
      Команда MOVS указывает два операнда памяти. Только MOVS и еще
    одна строковая команда CMPS работают с двумя операндами памяти. Все
    остальные команды требуют, чтобы один или оба операнда находились в
    регистре микропроцессора. Как и команды LODS и STOS, команда MOVS
    работает как с байтами, так и со словами. Поскольку строковые
    команды имеют дело с жестко заданными адресами, для определения
    типов служат только операнды, написанные программистом. Команда
    должна иметь оба операнда, и оба они должны быть одинаковых типов,
    иначе программист может указать тип пересылки частью кода операции,
    т.е. команда MOVSB и случае байтовых строк или команда MOVSW для
    строк, состоящих из слов. Если в программе используется основная
    форма, команда MOVS, ассемблер проверяет переменные на правильность
    сегментной адресации, а также проверяет их типы.
 
      Комбинация команды MOVS с префиксом REP дает эффективную
    команду пересылки блока. Имея счетчик в регистре CX и показывающий
    направление пересылки флаг направления, команда REP MOVS пересылает
    данные из одного места памяти в другое очень быстро.
    Микропроцессор, выполняющий команду REP MOVS, пересылает данные с
    максимально возможной скоростью. Он больше не выбирает никакие
    команды, поскольку единственное, что делается во время такой
    пересылки - это пересылка.
 
      Установка флага направления критична для правильной работы
    команды REP MOVS. Различные виды установки флага направления
    обсуждались в гл.3 именно на примере команды пересылки, и в
    программе необходимо придерживаться рекомендаций, данных в этой
    главе, особенно, если поля источника и результата перекрываются.

Команды сканирования и сравнения


    Две оставшиеся строковые команды используются в программах для
    сравнения строковой информации.  Первая из них - команда,
    сканирование строки SCAS.  Эта команда сравнивает значение в
    регистре AL или регистре AX с операндом в памяти, на который
    ссылается пара регистров ES:DI.  Команда SCAS устанавливает флаги
    нуля, переноса и переполнения, показывая результат сравнения
    аккумулятора и ячейки памяти, и изменяет регистр DI так, чтобы он
    указывал на следующий операнд в строке.
 
      Команда SCAS не может использовать обычный префикс REP для
    сканирования длинной строки информации. Точно так же, как команда
    REP LODS не имеет смысла, команда REP SCAS не позволяет программе
    контролировать каждое сравнение. Вместо этого существует два
    варианта префикса REP - "повторять пока равно" REPE и "повторять
    пока не равно" REPNE. Как и в случае обычного префикса REP,
    программа загружает в регистр CX длину строки. Если указан префикс
    REPE, команда выполняется ло тех пор, пока содержимое регистра AL
    (или AX) не перестанет совпадать с ячейками памяти, или пока
    содержимое регистра CX не станет равно 0. Пока аккумулятор
    совпадает с ячейкой памяти, сканирование продолжается. Команда
    REPNE в точности противоположна команде REPE. Сканирование
    продолжается до тех пор, пока аккумулятор не совпадает с ячейкой
    памяти.
 
      Комбинация команд SCAS и REPNE позволяет программе выполнять
    быстрый поиск по таблице. Чтобы найти объект в таблице, программа
    должна перебрать каждую ячейку для сравнения с аргументом. На
   Фиг. 4.24 показано, как команда SCAS выполняет эту функцию. В
    регистре AL содержится аргумент сравнения. Таблица SCAN_TABLE
    содержит значения, среди которых ведется поиск, а в регистре CX
    находится длина таблицы. Команда REPNE SCASB сканирует таблицу до
    тех пор, пока содержимое аккумулятора не станет равно элементу
    строки. В этом месте регистр DI указывает на байт таблицы,
    непосредственно следующий за сравнением. Вы можете определить
    смещение совпавшего объекта, вычитая единицу из регистра DI после
    метки FOUND. Программа может использовать эту информацию для
    доступа к другой таблице, или таблицам, которые содержат
    информацию, соответствующую этим исходным данным. Нужно обратить
    особое внимание на команду JE после команды сканирования.
    Существуют два случая, в которых управление передается этой
    команде: байт в строке совпал с регистром AL и условие, задаваемое
    префиксом REPNE, больше не выполняется; либо регистр CX достиг
    нулевого значения без нахождения соответствующего числа в таблице.
    В некоторых случаях создаются ситуации, исключающие появление
    второго условия. Но в большинстве программ, необходимо учитывать
    возможность неверных исходных данных. Программа перейдет на метку
    FOUND после команды сканирования, если команда установила флаг нуля
    (или равенства). Тем самым гарантируется, что сравнение найдено.
    Если же регистр CX достиг нуля, последняя итерация сканирования
    сбросила флаг нуля, показывая, что соответствия нет.

           Microsoft (R) Macro Assembler Version 5.00              1/1/80 04:01:36
           Фиг. 4.24 Поиск в таблице                              Page   1-1
 
 
                                         PAGE    ,132
                                         TITLE   Фиг. 4.24 Поиск в таблице
            0000                   CODE    SEGMENT
                                         ASSUME  CS:CODE,DS:CODE,ES:CODE
 
                                   ;--------------------------------------
                                   ; Поиск значения AL в таблице
                                   ;--------------------------------------
 
            0000  8D 3E 000C R                 LEA     DI, SCAN_TABLE        ; Адрес таблицы
            0004  B9 000B 90                   MOV     CX, SCAN_TABLE_LENGTH   ; Длина таблицы
            0008  F2/ AE                 REPNE   SCASB                       ; Поиск
            000A  74 00                  JE      FOUND                 ; Если равно, то значение найдено
                                   ; ...                         ; Иначе значение не найдено
            000C                   FOUND:
                                   ;-----  продолжение программы
 
            000C  89 96 93 8A 85 8D 83     SCAN_TABLE      DB      'ЙЦУКЕНГШЩЗХ'
                98 99 87 95
 
            = 000B                       SCAN_TABLE_LENGTH       EQU     $-SCAN_TABLE
 
            0017                   CODE    ENDS
                                         END
 
            Фиг. 4.24 Сканирование таблицы
      Последняя строковая команда - сравнение строк CMPS. Подобно
    сканированию строки, это - команда сравнения. Подобно команде MOVS,
    она работает с двумя операндами памяти. Команда CMPS сравнивает
    строку по адресу DS:SI со строкой по адресу ES:DI, и соответственно
    устанавливает флаги. Как и для команды SCAS, в данном случае
    использовать префикс REP нельзя, а префиксы REPE и REPNE можно
    использовать беспрепятственно.

          Microsoft (R) Macro Assembler Version 5.00              1/1/80 04:01:41
          Фиг. 4.25 Сравнение строк                         Page  1-1
 
                                        PAGE    ,132
                                        TITLE   Фиг. 4.25 Сравнение строк
           0000                   CODE    SEGMENT
                                        ASSUME  CS:CODE,DS:CODE,ES:CODE
 
                                  ;--------------------------------------
                                  ; Сравнивается 5-символьная строка с таблицей
                                  ; таких 5-символьных строк. Выход из программы
                                  ; если найдена искомая строка в таблице строк.
                                  ;--------------------------------------
 
           0000                   FIG4_25 PROC    NEAR
           0000  8D 36 001D R                 LEA     SI, ARGUMENT          ; Адрес строки
           0004  8D 3E 0022 R                 LEA     DI, COMPARE_TABLE     ; Адрес таблицы
           0008  BB 0000                      MOV     BX, 0           ; В BX cчетчик просмотренных строк
           000B                   COMPARE_LOOP:
           000B  56                           PUSH    SI                    ; Сохранение адреса строки
           000C  57                           PUSH    DI                    ; Сохранение адреса таблицы
           000D  B9 0005                      MOV     CX, 5           ; Сравниваются 5 байт
           0010  F3/ A6           REPE    CMPS    ARGUMENT,COMPARE_TABLE  ; Сравнение
           0012  5F                           POP     DI                    ; Восстановление
           0013  5E                           POP     SI                    ;  регистров
           0014  74 06                  JE          FOUND             ; Искомая строка найдена
           0016  83 C7 05                     ADD     DI, 5           ; Сдвиг указателя на следующую
                                                                ;  строку в таблице
           0019  43                           INC     BX                    ; Номер текущей строки в таблице
           001A  EB EF                  JMP     COMPARE_LOOP          ; Цикл
 
           001C                   FOUND:
           001C  C3                           RET
           001D                   FIG4_25 ENDP
 
           001D  41 42 43 44 45         ARGUMENT          DB          'ABCDE'
           0022                   COMPARE_TABLE   LABEL   BYTE
           0022  51 57 45 52 54 50 4F         DB          'QWERT','POIUY','ASDFG','LKJHG'
               49 55 59 41 53 44 46
               47 4C 4B 4A 48 47
           0036  5A 58 43 56 42 4D 4E         DB          'ZXCVB','MNBVC','VWXYZ','ABCDE'
               42 56 43 56 57 58 59
               5A 41 42 43 44 45
           004A                   CODE    ENDS
                                        END
 
            Фиг. 4.25 Сравнение строк
      Фиг. 4.25 демонстрирует пример использования команды CMPS.
    Этот пример сравнивает пятисимвольную исходную строку с таблицей
    строк символов. Программа пытается найти соответствие исходной
    строки с элементом таблицы. Когда строка найдена, в регистре BX
    нахолится индекс строки. В программе используется префикс REPE, так
    что команда сравненния строк выполняется до тех пор, пока один из
    символов аргумента не совпадает с символом таблицы. Если все пять
    символов совпали, программа находит правильный элемент. Команда JE
    ("переход, если равно") проверяет результат команды CMPS. Если
    сравнение завершилось из=за несоответствия символов, флаг нуля
    показывает ненулевое состояние. Если же команда CMPS завершилась
    потому, что счетчик CX стал нулевым, флаг нуля покажет совпадение и
    произойдет переход на метку FOUND. Вы можете заметить, что в этом
    примере отсутствуют некоторые необходимые детали, которые смогли бы
    сделать его хорошей программой. Например, он никак не обрабатывает
    случай, когда исходная строка не совпала ни с одним элементм
    таблицы. Любой хороший программист скажет вам, что исключительные
    ситуации нужно обрабатывать всегда.

Команды передачи управления


      Команды передачи управления нужны для того, чтобы передавать
    выполнение программы в различные секции команд. В их число входят
    также команды вызова подпрограмм. Команды вызова подпрограмм
    вызывают подпрограммы, а команды перехода передают управление
    поименованной ячейке без сохранения адреса возврата. Команда
    условного перехода позволяет ЭВМ думать. Условные команды могут
    проверить результат предыдущих действий и изменить течение
    программы на основе полученного результата. Если бы команды
    условного перехода не сеществовали, программирование для ЭВМ было
    бы много проще, но и менне продуктивно.
 
      Первое, что нужно рассмотреть при обсуждении команд передачи
    управления - это методы адресации, используемые для определения
    адреса ячейки, куда передается управление. Хотя операнд команды
    перехода - такая же ссылка к памяти, как и ссылка к данным,
    программы используют адреса перехода иначе, чем адреса данных.
    Поэтому для адресации точки перехода существуют лучшие способы.

Близкие и далекие переходы


    Команды перехода модифицируют указатель команды IP, и, возможно,
    регистр сегмента кодов CS.      Эти регистры показывают, какая
    следующая команда должна быть выполнена.  Команда перехода является
    специальным случаем пересылки MOV данных в регистр или пару
    регистров; и некоторые ЭВМ действительно выполняют команду перехода
    именно таким способом.  Однако способы загрузки пары регистров
    CS:IP в микропроцессоре 8088 во многом отличаются от способов,
    используемых для других регистров.
 
      Прежде всего мы должны ввести некоторые определения. Если
    команда перехода изменяет только регистр IP, это близкий переход
    (NEAR=переход), так как переход происходит внутри сегмента. Если
    переход изменяет регистр CS, это далекий FAR=переход.
      Аттрибуты NEAR и FAR используются при работе ассемблера. Любая
    программная метка в программе на языке ассемблера имеет атрибут
    либо NEAR, либо FAR, так же, как данные имеют атрибуты BYTE или
    WORD. В некоторых примерах этой главы имеются процедуры, которые
    используют атрибут NEAR в операторе PROC. Это означает, что метка,
    связанная с оператором PROC (имя процедуры) имеет атрибут NEAR.
    Ассемблер использует эту информацию для того, чтобы определить,
    какой тип команды перехода или вызова породить при переходе к этой
    метке. Поскольку большинство процедур - подпрограммы, атрибут NEAR
    или FAR оператора PROC также определяет тип порождаемой команды
    возврата. Вызов FAR=процедуры сохраняет значения как регистра CS,
    так и регистра IP, тогда как вызов NEAR=процедуры оставляет в стеке
    только значение регистра IP. Команда возврата должна учитывать,
    какой тип вызова юыл сделан, чтобы подпрограмма могла вернуться к
    правильному месту.

Адресация переходов


    Если адрес перехода или вызова подпрограммы является частью самой
    коамнды (как данные в командах с непосредственным операндом), это -
    непосредственный переход.  Если адрес перехода команды содержится в
    регистре или ячейке памяти, это - косвенный переход, так как
    команда требует загрузки адреса, извлекаемого из некоторого
    промежуточного места хранения; программа не может перейти прямо в
    необходимое место, и должа идти туда косвенно.
 
      Существует два метода вычисления адреса перехода. Если в
    команде указано значение адреса, это абсолютный переход, т.е.
    переход по абсолютному адресу. Команда может указать место
    перехода, как некоторое расстояние от нее самой. Этот метод
    перехода называется относительным переходом.
 
      Преимущество относительных переходов заключается в том, что
    программа наиболее часто переходит к близлежащим ячейкам; команда
    перехода может использовать однобайтовое смещение. Если смещение
    трактуется, как число в дополнительном коде, то двухбайтовая
    команда относительного перехода (один байт - код операции, и один
    байт - смещение) может выполнить переход на 127 байт вперед или на
    128 байт назад внутри программы. Микропроцессор 8088 имеет два типа
    относительных переходов: один имеет однобайтовое смещение, другой -
    двухбайтовое.
 
      В микропроцессоре 8088 все условные переходы имеют однобайтовое
    смещение. Иногда это неудобно, например в случае условного перехода
    к ячейке, находящейся на расстоянии в 150 байт от текущего места.
    В таких случаях программа должна использовать пару переходов,
    условный и безусловный; далее приводится пример такого метода
    перехода. В обычных же случаях однобайтовые смещения условных
    переходов в микропроцессоре 8088 минимизируют объем программы,
    необходимой для реализации любой заданной функции.
 
      При расчете смещения относительного перехода микропроцессор
    8088 отсчитывает смещения от значения указателя команд, которое
    получится после выполнения команды. Фиг. 4.26 показывает разные
    примеры команд относительного перехода. Если точка перехода следует
    непосредственно за переходом, смещение равно 0. При переходе к
    самой команде перехода смещение равно -2. При двухбайтовом смещении
    переход может быть сделан в диапазоне -32768 - 32767 байт от
    значения регистра IP после выполнения команды перехода.

Безусловные переходы


    Безусловные переход - это такой переход, который передает
    управление всякий раз, когда он выполняется.  Наоборот, услловный
    переход проверяет текущее состояние машины, чтобы определить,
    передавать управление или нет.  Существует два вида команд
    безусловной передачи управления - команды переходов и вызовов.
 
      Все команды вызова CALL - безусловны. Различные команды CALL
    показаны на Фиг. 4.27. Близкий вызов CALL, или NEAR CALL, указывает
    новое значение регистра IP и сохраняет старое значение регистра IP
    в стеке в качестве адреса возврата. Далекий вызов CALL, или FAR
    CALL, задает новые значения сегмента и смещения для дальнейшего
    выполнения программы и сохраняет в стеке как регистр IP, так и
    регистр CS. Близкий непосредственный вызов CALL - это относительный
    переход, использующий двухбайтовое поле смещения. Все остальные
    команды вызова - абсолютные переходы. Непосредственный вызов FAR
    CALL требует четырехбайтовое поле операнда для указания новых
    значений для регистров CS и IP. Косвенные переходы используют байт
    адресации mod=r/m для указания операнда=регистра или памяти; этот
    операнд содержит адрес подпрограммы. Косвенные вызовы типа NEAR
    загружают однословный операнд в регистр IP. Вызовы типа FAR
    загружают двойное слово из памяти в пару регистров CS:IP; первое
    слово загружается в регистр IP, а второе - в регистр CS. Если
    команда указывает регистр в качестве операнда косвенного далекого
    вызова, результат непредсказуем; микропроцессор 8088 берет новое
    значение регистра CS неизвестно откуда. Ни в коем случае нельзя
    использовать эту модификацию команды.
 
      Командам CALL соответствуют команды возврата RET. Все возвраты
    - косвенные переходы, поскольку они извлекают адрес перехода из
    вершины стека. Близкий возврат извлекает из стека одно слово и
    помещает его в регистр IP, а далекий возврат извлекает два слова,
    помещая слово из меньшего адреса в регистр IP, а слово из большего
    адреса в регистр CS.
 
      Программы могут модифицировать возвраты как типа NEAR, так и
    типа FAR, указывая параметр счетчика байтов. Команда возврата
    прибавляет его значение к указателю стека после извлечения из него
    адреса (адресов) возврата. Такая команда позволяет программе
    удалять параметры из стека без использования специальных команд
    POP; тем самым подчеркивается, что стек - носитель передаваемых
    подпрограмме параметров. Такой стиль работы со стеком мы уже
    обсуждали во всех подробностях ранее в разделе "Работа со стеком".
 
      Команды безусловного перехода JMP идентичны командам CALL по их
    возможностям адресации. Однако существует дополнительная команда
    перехода, указывающая однобайтовое смещение для близкого
    относительного перехода (команда короткого перехода).
    Соответствующей ей команды CALL не существует, так как вызовы
    подпрограмм, расположенных поблизости, происходят очень редко.
    Команды переходов используют те же методы генерации адреса, что и
    команды вызова.
 
      Сделаем сдесь замечание об оптимизации кода и о том, как
    работает ассемблер. По мере того, как ассемблер делает первый
    переход по тексту программы и назначает адреса командам, он должен
    решить, использовать двух= или трехбайтовую разновидность команды
    JMP. Если это переход назад, т.е. на место, уже известное
    ассемблеру, он может определить правильное смещение; тем самым
    ассемблер знает, находится ли переход в диапазоне короткого
    смещения. Однако, если переход делается вперед, на метку, о которой
    ассемблер еще не знает, он должен предположить, что метка находится
    далее, чем 128 байт от текущего места. Затем ассемблер порождает
    длинную форму команды перехода. Худший случай ассемблер обязан
    выбирать потому, что потом уже не может возвратиться назад и
    увеличить размер команды. Затем ассемблер заместит трехбайтовую
    команду перехода двухбайтовой командой JMP и однобайтовой командой
    NOP, если обнаружит, что переход делается ближе 128 байт от
    текущего места. Так как такой переход выполняется несколько
    быстрее, время выполнения в этом случае сокращается, но объектный
    код остается больше необходимого.
 
      Если программисту заранее известно, что переход вперед делается
    на место, лежащее в диапазоне 128 байт от текущего места, он может
    об этом сообщить ассемблеру с помощью следующей строки:
      JMP SHORT LABEL
 
      Аттрибут SHORT заставляет ассемблер сформировать короткую форму
    SHORT команды перехода, даже если он еще не встречал метку. Если же
    программист сделал ошибку и переход в действительности не может
    быть коротким, ассемблер выдает сообщение об ошибке. На Фиг. 4.26
    дан пример оператора SHORT.
 
      Фиг. 4.28 показывает, как можно устроить таблицу переходов
    с помощью команды косвенного перехода. В этом примере делается
    выбор среди нескольких программ, основываясь на значении аргумента
    в регистре AL. Аналогичная программа могла бы вызвать подпрограмму
    по индексу. Это - реализация на языке ассемблера оператора CASE,
    который существует в некоторых языках высокого уровня.

Переходы по условию


    Условные переходы делятся на две группы:  проверяющие результаты
    предыдущей арифметической или логической команды, и управляющие
    итерациями фрагмента программы.  Все условные преходы имеют
    однобайтовое смещение.  Если условный переход осуществляется на
    место, находящееся дальше 128 байт, нужно использовать специальную
    конструкцию.  Например, допустим, что программе надо перейти к
    метке ZERO, если установлен флаг нуля; эта метка находится дальше
    128 байт от текущего места.  Программа в этом случае выглядит
    примерно так:
      JNZ    CONTINUE
      JMP    ZERO
        CONTINUE:
           Microsoft (R) Macro Assembler Version 5.00              1/1/80 04:01:56
           Фиг. 4.28 Таблица переходов                      Page   1-1
 
 
                                         PAGE    ,132
                                         TITLE   Фиг. 4.28 Таблица переходов
 
            0000                   CODE    SEGMENT
                                         ASSUME  CS:CODE
 
                                   ;----------------------------------------
                                   ; В этом примере демонстрируется программа,
                                   ; осуществляющая переход в зависимости от
                                   ; значения регистра AL. В регистре находится
                                   ; индекс в таблице переходов необходимой программы
                                   ;----------------------------------------
 
            0000  2A FF                  SUB     BH, BH                ; BH <- 0
            0002  8A D8                  MOV     BL, AL                ; Индекс загружается в регистр BL
            0004  D1 E3                  SHL     BX, 1                 ; * 2 для получения смещения
            0006  2E: FF A7 000B R             JMP     CS:[BX + BRANCH_TABLE]  ; Косвенный близкий переход
 
            000B                   BRANCH_TABLE    LABEL   WORD
            000B  0011 R                       DW      ROUTINE_ONE
            000D  0011 R                       DW      ROUTINE_TWO
            000F  0011 R                       DW      ROUTINE_THREE
                                   ; ...
 
            0011                   ROUTINE_ONE     LABEL   NEAR
            0011                   ROUTINE_TWO     LABEL   NEAR
            0011                   ROUTINE_THREE   LABEL   NEAR
 
            0011                   CODE    ENDS
                                         END
 
            Фиг. 4.28 Таблица переходов
 
      Здесь используется условный переход с противоположным условием.
    На метку ZERO управление передает команда безусловного перехода,
    которая может использовать смещение вплоть до 32768 байт, а в
    условном переходе используется метка CONTINUE.
 
      Если целью является минимизация программ, этого метода нужно
    избегать, так как он превращает команду условного перехода в
    пятибайтовую последовательность. Иногда реорганизация программы
    приводит к тому, что место перехода попадает в нужный диапазон.
    Однако не стоит особенно стараться минимизировать программу. В
    большинстве случаев не имеет особого значения, насколько у
    программы большой объем, лишь бы он не превышал заданного. Это
    имело бы смысл, если бы вы пытались сделать нечто помещающееся в
    модуль ПЗУ постоянного объема, но обычно усилия, затрачиваемые на
    изменения программы, не дают ощутимого выигрыша.

Проверки кода условия


    Первая группа команд условного перехода проверяет текущее состояние
    регистра флагов.  Затем в зависимости от кодов условия команда
    делает переход (или не делает).  Команды условного перехода не
    устанавливают флаги, а только проверяют их текущее состояние.
    Ранее рассмотренные арифметические и логические команды
    устанавливают флаги.  В последующих примерах предположим, что
    команда сравнения CMP уже установила флаги.
 
      На Фиг.4.29 показаны команды условного перехода и проверяемые
    ими флаги. В строках рисунка перечислены команды условного
    перехода, а в пяти колонках показано состояние флагов. Буква X в
    любой позиции означает, что команда не проверяет флаг. Цифра 0
    означает, что этот флаг должен быть сброшен, чтобы условие было
    выполнено и переход произошел. Цифра 1 означает, что флаг должен
    быть установлен, чтобы переход произошел. Некоторые элементы
    таблицы показывают выражение, которое должно быть истинно, чтобы
    переход произошел. Так сделано для арифметических переходов, и мы
    обсудим их далее более подробно.
 
      На Фиг.4.29 условные переходы разделены на три группы:
    непосредственно проверяющие один из флагов; делающие арифметическое
    сравнение без знака; делающие арифметическое сравнение со знаком.
 
      На Фиг.4.29а показана проверка отдельных флагов. Условный
    переход может проверить каждый из этих пяти флагов непосредственно
    на 0 или 1. Проверка флага переноса показана на рисунке в группе
    арифметики без знака, поскольку она имеет еще и арифметический
    смысл. Заметим, что многие команды условного перехода имеют более
    одной мнемоники, если даже выполняется одна и та же проверка.
    Например, проверку флага нуля осуществляет команда JZ (переход,
    если результат операции равен нулю). Однако команда JE (переход,
    если равно) также порождает ту же команду. Следующая
    последовательность поясняет смысл этого:
 
       CMP AX, BX
       JE LABEL
 
      Команда CMP вычитает содержимое регистра BX из содержимого
    регистра AX, устанавливая флаги в соответствии с результатом. Так
    как результат нулевой, если оба операнда равны, флаг нуля
    показывает равенство. Аналогично, команда JNZ (переход, если не
    нуль) идентичен команде JNE (переход, если не равно). Команда JP
    (переход по четности) - то же самое, что и команда JPE (переход при
    наличии четности); команда JNP (переход по нечетности) - то же
    самое, что команда JPO (переход при отсутствии четности).
     Команды                         Флаги
     условного
     перехода     OF    CY     Z        P    S    Комментарий
    -------------------------------------------------------------------
     JE/JZ  X    X     1      X        X
     JP/JPE X    X     X      1        X
     JO      1    X     X      X        X
     JS      X    X     X      X        1
     JNE/JNZ      X    X     0      X        X
     JNP/JPO      X    X     X      0        X
     JNO    0    X     X      X        X
     JNS    X    X     X      X        0
                              (a)
 
     JL/JNGE      a    X      X     X        b   a NEQ b
     JLE/JNG      a    X      1     X        b   Z OR (A NEQ B)
     JNL/JGE      a    X      X     X        b   a = b
     JNLE/JG      a    X      0     X        b   (NOT Z) AND (a=b)
                              (b)
 
     JB/JNAE/JC  X      1      X     X        X
     JBE/JNA      X    1      1     X        X   CY OR Z
     JNB/JAE/JN  X      0      X     X        X
     JNBE/JA      X    0      0     X        X   (NOT CY) AND (NOT Z)
                              (c)
    ------------------------------------------------------------------
 
       Фиг. 4.29 Проверка флагов перехода по условию. (a) Проверка флага
       (b) Арифметика со знаком; (c) беззнаковая арифметика.
 
      Следующую группу команд условного перехода на Фиг.4.29б
    составляют арифметические сравнения со знаком. Существуют четыре
    условия, которые могут быть проверены: меньше (JL), меньше или
    равно (JLE), больше (JG), больше или равно (JGE). Другие четыре
    мнемоники - отрицания этих четырех. В случае арифметики со знаком
    ассемблер использует мнемонику в названии команд "меньше" (less) и
    "больше" (greater). Далее мы увидим, что для арифметики без знака
    ассемблер использует мнемонику "выше" (above) и "ниже" (below).
 
      Арифметические выражения можно понять, используя их вместе с
    командой CMP. Например,
 
      CMP   AX,BX
      JL    LABEL
 
      Преход произойдет, если содержимое регистра AX меньше
    содержимого регистра BX. Вы можете читать комбинацию команд
    сравнения и условного перехода вместе, как один оператор: операнд
    результата встречается первым, затем идет условный оператор, а за
    ним следует исходный операнд. Другой пример:
 
      CMP    CX,WORD_IN_MEMORY
      JNLE   LABEL
      - можно прочитать так: "переход, если содержимое регистра CX не
    меньше, чем, или равен содержимому ячейки памяти WORD_IN_MEMORY.
    Этот прием можно использовать для определения значения любой команды
    арифметического перехода, учитывающей знак или не учитывающей.
 
      Как показано на Фиг.4.29б арифметические сравнения со знаком
    проверяют одновременно несколько флагов. Фактически каждая из этих
    команд проверяет некоторую комбинацию флагов переполнения, знака,
    и, возможно, флага нуля. Например, в команде JL требуется, чтобы
    флаги переполнения и знака имели разные значения. Если они имеют
    одинаковое значение, первый операнд не был меньше второго.
    Рассмотрим эту операцию несколько подробнее, чтобы понять работу
    арифметических сравнений.
 
      Когда сравниваются два числа со знаком, возможны четыре
    комбинации флагов знака и переполнения. Рассмотрим каждую из
    четырех комбинаций, чтобы определить какое состояние операндов
    привело к данному результату. Предположим, что в каждом случае
    флаги установила команда CMP, вычитавшая два операнда.
 
      Знак S = 0, переполнение O = 0.
 
      Условие S=0 означает, что результат вычитания положителен.
    Условие O=0 означает, что переполнения не было, т.е. результат,
    представленный в дополнительном коде, правильный. Вычитание двух
    чисел, дающее положительный результат, показывает, что первое число
    больше второго, и поэтому имеет место соотношение "больше". Однако
    вычитание двух равных чисел также дает положительный результат, так
    что условие S=0, O=0 означает "больше или равно".
 
      S=1, O=0
 
      В этом случае O=0 означает, что результат верен, а S=1 говорит
    о том, что он отрицателен. Чтобы получить отрицательный результат,
    большее число должно вычитаться из меньшего, и соотношение означает
    "меньше".
 
      S = 0, O = 1
 
      Здесь O=1 показывает, что результат неверен, т.е. вышел за
    пределы возможностей разрядной сетки. Это значит, что сложение двух
    положительных чисел дало отрицательный результат или наоборот. В
    данном случае это сравнение показывает, что знак результата
    неверен; поэтому результат этого сравнения идентичен случаю, когда
    S=1, O=0, что означает "меньше".
 
      S = 1, O = 1
 
      Снова O=1 говорит о том, что знак результата неверен. Поэтому
    вычитание должно было привести к очень большому положительному
    числу, и соотношение будет "больше или равно".
 
      В некоторых случаях также учитывается флаг нуля. Например,
    команда JLE выполняется, если условие есть "меньше" (знак и
    переполнение разные) или "равно" (флаг нуля равен 1). Эти три флага
    позволяют микропроцессору 8088 проверить все возможные комбинации
    чисел со знаком.
 
      Последняя часть таблицы (Фиг.4.29в) показывает условия,
    проверяемые для арифметики без знака. Как и в случае арифметики со
    знаком, существуют четыре возможные соотношения между операндами,
    которые может проверить микропроцессор. Для того чтобы отличить
    команды условного перехода ориентированные на беззнаковую
    арифметику от знаковой арифметики, используются слова "выше" и
    "ниже" в названии команд. Вероятно, этим выражается точка зрения
    создателей набора команд, заключающаяся в том, что арифметика без
    знака будет использоваться в программах для вычисления адресов, а
    отрицательных адресов не бывает. "Выше" и "ниже" показывают
    расположение значения адресов внутри адресного пространства, в то
    время как "больше" и "меньше" говорит о соотношении чисел со
    знаком. Здесь важно, что выполняется именно та команда, которая
    указана в программе на языке ассемблера, независимо от типов
    сравниваемых операндов. Например, программа сравнивает два числа со
    знаком, а использует команду JA (переход, если выше).
    Микропроцессор выполняет условный переход в зависимости от
    соотношения двух чисел, считая их числами без знака, т.е. именно
    программист обязан выбрать правильную команду условного перехода.
 
      Микропроцессор 8088 при сравнении двух чисел без знака
    учитывает только два флага. Флаг переноса показывает, какое из
    чисел больше. Сравнение устанавливает флаг переноса, если первый
    операнд ниже второго или сбрасывает флаг переноса, если первый
    операнд либо выше, либо равен второму операнду, и флаг нуля
    определяет, что в данном случае верно.
 
      Сравнения без знака можно читать так же, как и сравнения со
    знаком. Например,
 
      CMP   AX,BX
      JA    LABEL
 
      - переход на метку LABEL происходит, если регистр AX выше
    регистра BX. Условный переход выполняется всегда, если объявленное
    соотношение существует между первым и вторым операндами
    предшествовавшей команды сравнения.

Управление циклами


    Существует несколько команд условного перехода, предназначенных для
    управления циклами в программах.  Поскольку программые циклы
    используются часто, желательно эффективное управление циклом.  На
    Фиг. 4.30 показаны четыре команды, созданные для того, чтобы
    облегчить программирование циклов на языке ассемблера
    микропроцессора 8088.
 
      Так же, как строковые команды используют регистр CX в качестве
    счетчика, команды цикла LOOP используют регистр CX в качестве
    счетчика цикла. Все эти команды неявно рассматривают регистр CX как
    счетчик итераций цикла. Простейшая команда среди них - команда
    LOOP. Команда LOOP уменьшает регистр CX и передает управление на
    метку, если содержимое регистра CX не равно 0. Если вычитание
    единицы из регистра CX не привело к нулевому результату, команда
    LOOP не делает перехода, и выполняется следующая команда.
 
      Приведенный ниже программный фрагмент демонстрирует обычное
    использование команды LOOP.
 
      MOV    CX,LOOP_COUNT
      BEGIN_LOOP:
      ; ...  тело цикла
      LOOP   BEGIN_LOOP
 

          Microsoft (R) Macro Assembler Version 5.00              1/1/80 04:02:01
          Фиг. 4.30 Команды цикла                           Page  1-1
 
 
                                        PAGE    ,132
                                        TITLE   Фиг. 4.30 Команды цикла
 
           0000                   CODE    SEGMENT
                                        ASSUME  CS:CODE
 
                                  ;----------------------------------------
                                  ; В этом примере демонстрируются команды цикла.
                                  ; Команды в примере не являются законченной программой.
                                  ;----------------------------------------
 
           0000  E3 06                  JCXZ    END_OF_LOOP     ; Конец цикла, если CX равно 0
 
           0002                   BEGIN_LOOP:
 
                                  ; ....  Тело цикла
 
           0002  E2 FE                  LOOP    BEGIN_LOOP      ; Переход пока регистр CX не станет равен 0
 
                                  ; ....  Если проверяется какое-либо условие, то
           0004  E1 FC                  LOOPE   BEGIN_LOOP      ; Переход по равенству в условии и
                                                          ;  значение регистра CX не равно 0
                                  ; ....  Или
 
           0006  E0 FA                  LOOPNE  BEGIN_LOOP      ; Переход по неравенству в условиии и
                                                          ;  значение регистра CX не равно 0
 
           0008                   END_OF_LOOP:
 
           0008                   CODE    ENDS
                                        END
 
            Фиг. 4.30 Команда цикла
 
      Программа помещает число итераций цикла в регистр CX перед
    выполнением цикла. Затем выполняется тело цикла, а следом за ним
    команда LOOP. Она уменьшает счетчик на единицу, что соответствует
    единственной, только что выполненной итерации цикла. Если теперь
    счетчик в регистре CX равен 0, программа продолжает выполняться
    после команды LOOP. Если счетчик не равен 0, управление
    возвращается к началу цикла, чтобы совершить еще один проход по
    телу цикла. Тело цикла выполняется столько раз, сколько было
    сначала задано содержимым регистра CX. Единственное важное
    замечание: если программа внутри цикла изменяет регистр CX, число
    итераций цикла не будет соответствовать начальному значению в
    регистре CX.
 
      Описанный метод одинаково хорошо работает, когда число циклов
    известно во время ассемблирования (как в примере, где LOOP_COUNT -
    непосредственно заносимое значение), и когда число циклов
    определяется во время выполнения. Если вычисленное число оказалось
    равным 0, цикл выполнится 65536 раз. Когда микропроцессор 8088
    выполняет первую команду LOOP, он уменьшает CX от 0 до 0FFFFH, и
    поскольку теперь регистр CX ненулевой, повторяет цикл. Таким
    образом, загрузка нулевого значения счетчика циклов - специальный
    случай. Этот специальный случай обрабатывается командой JCXZ
    (переход, если содержимое регистра CX равно 0). Эта команда
    проверяет текущее содержимое регистра CX, и делает переход, если
    оно равно нулю. Команда не проверяет ни одного флага, и не влияет
    ни на один из них. Следующий пример аналогичен предыдущему, за
    исключением того, что он загружает регистр CX из ячейки памяти,
    содержимое которой вычисляется во время выполнения программы. По
    этой причине может оказаться, что счетчик циклов нулевой, и пример
    использует команду JCXZ, чтобы проверить, нужно ли полностью
    пропустить тело цикла.
 
         MOV         CX,LOOP_COUNT_WORD
         JCXZ    END_OF_LOOP
      BEGIN_LOOP:
         ;   ... тело цикла
         LOOP    BEGIN_LOOP
      END_OF_LOOP:
 
      В программе не нужно использовать команду JCXZ в каждом цикле с
    вычисляемым счетчиком. Если программист знает, что счетчик циклов
    никогда не будет равен нулю, проверка не нужна. Однако опыт
    показывает, что значение, которое "никогда" не должно появиться,
    обычно появляется в первую очередь, как только вы начинаете
    выполнять программу.
 
      Оставшиеся две команды цикла предоставляют еще большие
    возможностей при управлении циклами. Эти команды аналогичны
    префиксам REPE и REPNE. Если команда LOOP выходит из цикла, только
    когда в регистре CX оказывается нуль, то команда LOOPE (цикл, пока
    равно) выходит из цикла, если установлен флаг нуля, или если в
    регистре CX получился 0. Тем самым становится возможным
    двойственное завершение цикла. Программа может загрузить в регистр
    CX максимальное число итераций цикла, а затем проверять флаг нуля в
    конце каждого цикла на условие завершения. Команда LOOPNE (цикл,
    пока не равно) выполняет обратную к описанной проверку флага нуля:
    цикл здесь завершается, если регистр достиг нуля, или если
    установлен флаг нуля.
      Следующий пример показывает использование команды LOOPNE. В
    примере складываются два списка чисел, чтобы найти пару элементов,
    сумма которых точно равна 100. Так как в каждой итерации перед
    проверкой складываются два чила, команду REPNE CMPSB использовать
    нельзя.
 
      В примере предполагается, что пары регистров DS:SI и ES:DI
    инициализированы так, чтобы указывать на эти списки.
 
      MOV   CX,MAX_LOOP_COUNT ;максимальное число заходов
      BEGIN_LOOP:
      LODSB               ;чтение числа из первого списка
      ADD   AL,ES:[DI]    ;прибавить из второго списка
      INC   DI            ;указатель на следующий элемент
      CMP   AL,100              ;проверка на нужное значение
      LOOPNE      BEGIN_LOOP    ;снова, если не равно и не все
      JE    MATCH_FOUND    ;переход сюда, чтобы определить конец

Установка флагов


    Есть три команды, которые непосредственно управляют состоянием
    флага переноса.  Команды STC, CLC, CMC соответственно могут
    устанавлмвать, сбрасывать и изменять флаг переноса.  Этот флаг -
    единственный, которому уделено такое внимание, и в первую очередь,
    благодаря важности флага переноса при операциях с повышенной
    точностью.    Флаг переноса критичен на промежуточных шагах любых
    многословных операций.  Возможность сбрасывать или устанавливать
    флаг переноса может помочь при циклической обработке с повышенной
    точностью.    На Фиг. 4.31 показан пример использования команды CLC.
    Цикл внутри примера складывает отдельные байты двух 10-разрядных
    упакованных десятичных чисел.  Программа выполняет цикл пять раз,
    так как за каждую итерацию она обрабатывает две цифры.  Информация
    о переносе из одного оборота цикла в другой передается через флаг
    переноса.  Команда CLC сбрасывает флаг переноса перед первым циклом
    для того, чтобы перед первым сложением не было переноса.  Флаг
    переноса также важен в операциях сдвига, где он становится девятым
    или семнадцатым битом регистра во время выполнения операции.
 
      Два флага состояния микропроцессора имеют специальные команды
    работающие с ними. Программа может установить или сбросить маску
    прерываний соответственно командами STI и CLI. Команда STI включает
    систему прерываний микропроцессора 8088, позволяя ему реагировать
    на внешние прерывания. Команда CLI блокирует систему внешних
    прерываний.
           Microsoft (R) Macro Assembler Version 5.00              1/1/80 04:02:06
           Фиг. 4.31 Десятичная арифметика повышенной точности    Page   1-1
 
                                         PAGE    ,132
                                         TITLE   Фиг. 4.31 Десятичная арифметика повышенной точности
 
            0000                   CODE    SEGMENT
                                         ASSUME  CS:CODE,DS:CODE
 
            = 0005                       NUMBER_LENGTH   EQU     5                 ; 5 байт для упакованного числа
            0000  0005[            NUMBER_ONE      DB      NUMBER_LENGTH DUP (?)
                   ??
                              ]
            0005  0005[            NUMBER_TWO      DB      NUMBER_LENGTH DUP (?)
                   ??
                              ]
                                   ;----------------------------------------
                                   ; Эта программа складывает два десятичных упакованных
                                   ; числа (NUMBER_ONE и NUMBER_TWO) и заносит результат
                                   ; в NUMBER_TWO.
                                   ;----------------------------------------
 
            000A                   START_ADD:
            000A  B9 0005                      MOV     CX, NUMBER_LENGTH           ; Определение длины чисел
 
                                   ;-----  Установка индексных регистров на младший байт складываемых чисел
 
            000D  8D 36 0004 R                 LEA     SI, NUMBER_ONE + NUMBER_LENGTH - 1
            0011  8D 3E 0009 R                 LEA     DI, NUMBER_TWO + NUMBER_LENGTH - 1
 
            0015  F8                     CLC                           ; Нет младших разрядов
 
            0016                   ADD_LOOP:
            0016  8A 04                  MOV     AL, [SI]        ; Взять байт из первого числа
            0018  12 05                  ADC     AL, [DI]        ; Добавить из второго с учетом переноса
            001A  27                           DAA               ; Коррекция до упакованного формата
            001B  88 05                  MOV     [DI], AL        ; Занесение байта результата
            001D  9C                           PUSHF             ; Сохранение флага переноса (CF)
            001E  4E                           DEC     SI        ; Сдвиг указателя первого числа
            001F  4F                           DEC     DI        ; Сдвиг указателя второго числа
            0020  9D                           POPF              ; Восстановление флагов
            0021  E2 F3                  LOOP    ADD_LOOP        ; Обработка следующего байта
 
            0023                   CODE    ENDS
                                         END
 
            Фиг. 4.31 Операции с BCD повышенной точности
 
      Программа может установить или сбросить флаг направления с
    помощью команд STD и CLD. Команда CLD сбрасывает флаг направления,
    приводя к тому, что строковые команды ведут обработку при
    возрастающих адресах памяти. Команда STD устанавливает флаг, в
    результате чего строковые команды уменьшают адресный указатель
    после каждого выполнения.

Специальные команды


    Команда NOP - еще одна удобная команда микропроцессора 8088.  Она
    не делает ничего - "нет операции".  Тщательный анализ машинных
    команд показывает, что это в действительности команда XCHG.  А
    именно, это
 
      XCHG AX,AX
 
      что эквивалентно "ничего не выполнять", и бывают ситуации,
    когда такую команду желательно использовать, чтобы выждать
    некоторое время. В небольшом цикле, предназначенном для
    определенной временной задержки, можно использовать команды NOP для
    заполнения тела цикла, достигая тем самым точной выдержки времени
    выполнения цикла (хотя цикл - не лучший способ временной задержки,
    если интервал не очень маленький). Разработчики IBM PC требуют
    использовать NOP в некоторых местах, чтобы удовлетворять
    определенным временным требованиям. Например, программа не может
    иметь доступ в схему таймера чаще, чем раз в одну микросекунду. Две
    последовательно идущие команды IN нарушают это требование, так что
    между командами IN должно быть выполнено несколько команд NOP.
 
      Команда HLP останавливает ЭВМ; после выполнеиня этой команды
    микропроцессор останавливается. Если прерывания заблокированы во
    время останова, ЭВМ полностью "замирает". В этой сиутации
    единственная возможность запустить ЭВМ заново - выключить питание и
    включить его снова. Однако, если прерывания были разрешены в момент
    останова микропроцессора, они продолжают восприниматься и
    управление будет передаваться обработчику прерываний. После
    выполнеиня команды IRET в обработчике программа продолжает
    выполнение с ячейки, следующей за командой HLT. Команду HLT можно
    использовать в мультизадачных системах, чтобы завершить текущую
    активную задачу, но это не всегда лучший способ такого завершения.
    Разработчики персональной ЭВМ используют команду останова только
    тогда, когда возникает катастрофическая ошибка оборудования и
    дальнейшая работа бессмысленна.
 
      Команда LOCK - это командный префикс, такой же, как подавление
    сегментации или REP-префикс. Она предназначена для
    мультипроцессорных систем, в которых несколько микропроцессоров
    могут одновременно работать с одними и теми же ячейками памяти.
    Префикс LOCK вынуждает микропроцессор 8088 захватить линии
    управления, и тем самым получить исключительное право достура в
    память на время обработки команды с префиксом. Лучший пример этого
    - установка- проверка флага в общей памяти.
 
      MOV   AL,1
    LOCK XCHG     AL,FLAG_BYTE
      CMP   AL,1
 
      В этом примере байт FLAG_BYTE содержит нулевой или единичный
    индикатор. Микропроцессор устанавливает флаг равным единице, когда
    входит в "критическую" область программы, где он выполняет
    некоторые системные действия, которые может выполнять в данный
    момент времени лишь один микропроцессор. Перед входом в
    "охраняемую" область микропроцессор должен проверить, не работает
    ли в ней другой микропроцессор. Если это так, он должен подождать
    перед входом; иначе он может войти в область. В примере перед
    командой XCHG используется префикс LOCK. Префикс LOCK дает
    микропроцессору право исключительного доступа в течение выполнения
    команды XCHG, которая читает содержимое ячейки памяти, а затем
    записывает данные в эту же ячейку. Команда XCHG записывает 1 из
    регистра AL в поле FLAG_BYTE, засылая его текущее значение в
    регистр AL. Теперь, если регистр AL содержит 1, то в "охраняемой"
    области находится другой микропроцессор, и проверяющий
    микропроцессор обязан ждать. Если регистр AL нулевой,
    микропроцессор может войти в "охраняемую" область, а команда XCHG
    уже установила поле FLAG_BYTE равным 1, чтобы больше ни один
    микропроцессор не смог войти туда. Префикс LOCK препятствует любому
    другому микропроцессору проверять поле FLAG_BYTE в течение
    короткого интервала времени между проверкой и установкой ячейки
    флага.
 
      К сожалению, описание работы префикса LOCK носит чисто
    теоретический характер. IBM PC не реализует аппаратные средства,
    необходимые для работы LOCK.
 
      Команда WAIT останавливает выполнение программы
    микропроцессором, аналогично команде HLT. Но в случае команды WAIT
    выполнение программы возобновляется, когда один из внешних выводов
    микропроцессора 8088, вывод TEST, становится активен. Если вывод
    TEST активен во время выполнения команды WAIT, остановки не
    возникает вообще. Если вывод TEST неактивен, микропроцессор ждет до
    тех пор, пока он не станет активен. Микропроцессор 8088 использует
    эту команду вместе с командой ESC, чтобы работать с арифметическим
    сопроцессором 8087.
 
      Команда ESC дает возможность расширить набор команд
    микропроцессора 8088 без изменений самого микропроцессора. Команда
    содержит поле режима адресации и может указать любую ячейку памяти
    с помощью обычных способов адресации микропроцессора 8088. Однако
    микропроцессор ничего не делает в случае этой команды, кроме того,
    что читает данные из соответствующей ячейки и просто их
    отбрасывает.
 
      Команда ESC позволяет другому микропроцессору, или так
    называемому сопроцессору, наблюдать за работой микропроцессора
    8088. Команда ESC активизирует сопроцессор, и он выполняет ее, как
    собственную. Если сопроцессору нужен адрес памяти, микропроцессор
    8088 выдает этот адрес в цикле фиктивного чтения. Затем сопроцессор
    может выполнять запись или чтение по этому адресу в зависимости от
    того, что ему нужно. Эффективность команды ESC станет очевидна в
    гл.7, где рассматривается арифметический сопроцессор 8087,
    сопроцессор микропроцессора 8088.