Команда пересылки
Команда 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.
Сложение
Команда 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 ;переход сюда, чтобы определить конец