main: |
;точка входа в программу |
. . . |
|
xor ax,ax |
|
mov al,25 |
|
mul rez_l |
|
jnc m1 |
;если переполнение, то на m1 |
mov rez_h,ah |
;старшую часть результата в rez_h |
m1: |
|
mov rez_l,al |
|
exit: |
|
mov ax,4c00h |
;стандартный выход |
int 21h |
|
end main |
;конец программы |
Здесь производится умножение значения в rez_l на число в регистре al. Согласно табл. 4.4, результат умножения будет располагаться в регистре al (младшая часть) и регистре ah (старшая часть). Для выяснения размера результата командой условного перехода jnc анализируется состояние флага cf, и если оно не равно 1, то результат остался в рамках регистра al. Если же cf = 1, то выполняется команда, которая формирует в поле rez_h старшее слово результата. Команда mov rez_l,al формирует младшую часть результата. Теперь обратите внимание на строку с директивой label в сегменте данных. Мы еще не раз будем сталкиваться с этой директивой. В данном случае она назначает еще одно символическое имя rez адресу, на который уже указывает другой идентификатор rez_l. Отличие заключается в типах этих идентификаторов — имя rez имеет тип слова, который ему назначается директивой label (имя типа указано в качестве операнда label). Введя эту директиву в программу, мы подготовились к тому, что, возможно, результат операции умножения будет занимать слово в памяти. Обратите внимание, что мы не нарушили принципа: младший байт по младшему адресу. Далее, используя имя rez, можно обращаться к значению в этой области как к слову.
Примечание. Для умножения сомножителей со знаком используется инструкция imul (см. уч. пос., ч.4).
4.65. Деление целых чисел без знака
Процессор 8086 может выполнять отдельные типы операций деления:
–16-битового значения на 8-битовое (знаковое и беззнаковое);
–32-битового операнда на 16-битовый операнд (знаковое и беззнаковое).
71
Здесь рассматриваются только варианты беззнакового деления (инструкция div). Для деления со знаком используется инструкция idiv (см.
уч. пос., ч.4).
В общем виде команда деления чисел без знака может быть представлена следующим образом:
div делитель
Делитель может находиться в памяти или в регистре и иметь размер 8 или 16 битов (для МП 8086) или размер 8, 16 или 32 бита (для МП 80386). Местонахождение делимого фиксировано и так же, как в команде умножения, зависит от размера операнда. Результатом команды деления являются значения частного и остатка.
Варианты местоположения и размеров операндов операции деления и результатов деления (МП 8086) показаны в табл. 4.5.
|
|
|
Таблица 4.5 |
Делимое |
Делитель |
Частное |
Остаток |
Слово (16 битов) |
Байт |
Байт |
Байт |
в регистре ax |
в регистре или ячейке |
в регистре al |
в регистре ah |
|
памяти |
|
|
Двойное слово (32 бита) |
Слово (16 битов) |
Слово (16 битов) |
Слово (16 битов) |
dx — старшая часть |
в регистре или ячейке |
в регистре ax |
в регистре dx |
ax — младшая часть |
памяти |
|
|
После выполнения команды деления возможно возникновение прерывания с номером 0, называемого “деление на ноль”. Этот вид прерывания относится к так называемым исключениям. Данная разновидность прерываний возникает внутри микропроцессора из-за некоторых аномалий во время вычислительного процесса. Прерывание 0, т.е. “деление на ноль”, при выполнении команды div может возникнуть по одной из следующих причин:
1)делитель равен нулю;
2)частное не входит в отведенную под него разрядную сетку, что может случиться в следующих случаях:
- при делении слова на байт, если значение делимого более чем в 256 раз больше значения делителя;
- при делении двойного слова на слово, если значение делимого более чем в 65 536 раз больше значения делителя;
- при делении учетверенного слова на двойное слово, если значение делимого более чем в 4 294 967 296 раз больше значения делителя.
72
4.66. Беззнаковое деление 16-битового значения на 8-битовое
При беззнаковом делении 16-битового значения на 8-битовое значение делимое должно быть записано в регистре ax. 8-битовый делитель может храниться в любом 8-битовом общем регистре или переменной в памяти соответствующего размера. Инструкция div всегда записывает 8-битовое частное в регистр al, а 8-битовый остаток – в ah. Например:
mov |
ax,51 |
; делимое |
|
mov |
dl,10 |
; делитель |
|
div |
dl |
; после выполнения деления в al – частное, в ah – остаток |
|
Заметим, |
что |
частное представляет собой 8-битовое значение. Это |
|
означает, что результат деления 16-битового операнда на 8-битовый операнд не должен превышать 255. Если частное слишком велико, то генерируется прерывание 0 (прерывание по делению на 0). Инструкции:
mov |
ax,0fffh |
mov |
bl,1 |
div |
bl |
генерируют прерывание по делению на 0 (как можно ожидать, прерывание по делению на 0 генерируется также, если 0 используется в качестве делителя).
Пример деления значения del (20 001) на значение delt (200):
stacksg |
segment para ‘Stack’ |
|
db 256 dup (0) |
||
stacksg |
ends |
|
datasg segment para ‘Data’ |
||
del_b |
label byte |
|
del |
dw 20001 |
|
delt |
db 200 |
|
datasg ends |
||
codesg segment para ‘Code’ ;сегмент кода |
||
main: |
;точка входа в программу |
|
. . .
xor ax,ax
;последующие две команды эквивалентны одной mov ax,del, но копируют слово побайтно
mov ah,del_b |
;старший байт делимого в ah |
mov al,del_b+1;младший байт делимого в al |
|
div delt |
;в al — частное (здесь 100), в ah — остаток (здесь 1) |
. . . |
|
end main |
;конец программы |
73
4.67. Беззнаковое деление 32-битового значения на 16-битовое
При делении 32-битового операнда на 16-битовый операнд делимое должно записываться в регистрах dx:ax. 16-битовый делитель может находиться в любом из 16-битовых регистров общего назначения или в переменной в памяти соответствующего размера. Например, в результате выполнения инструкций:
mov ax,2
mov dx,1 ; в (dx:ax) загрузить 10002h mov bx,10h
div bx
частное 1000h (результат деления 10002h на 10h) будет записано в регистре ax, а 2 (остаток от деления) – в регистре dx.
Как и при умножении, при делении имеет значение, используются операнды со знаком или без знака. Для деления беззнаковых операндов используется операция div, а для деления операндов со знаком – idiv (см.
уч. пос., ч.4).
4.68. Изменение знака
Фактически инструкция neg операнд выполняет одно действие: операнд = 0 – операнд, то есть вычитает операнд из нуля.
Команду neg операнд можно применять:
–для смены знака;
–для выполнения вычитания из константы.
Смена знака. Инструкция neg позволяет изменить (инвертировать) знак содержимого общего регистра или переменной в памяти. Например, после выполнения инструкций:
mov |
ax,1 ; установить в регистр ax значение 1 |
neg |
ax ; отрицание ax, он становится равным -1 |
mov |
bx,ax ; скопировать содержимое ax в bx, т.е. bx :=ax=-1 |
neg |
bx ; отрицание bx, он становится равным 1 |
в регистре ax будет содержаться значение -1, а в регистре bx – значение 1. Вычитание из константы. В связи с тем, что команды sub и sbb не позволяют вычесть что-либо из константы, так как константа не может служить операндом-приемником в этих операциях, данную операцию
выполняют с помощью двух команд:
neg ax |
;смена знака содержимого регисра ax (т.е. ax:=-ax) |
add ax,340 |
;фактически вычитание: ax := 340-ax |
4.69. Преобразование размеров беззнаковых данных
Что делать, если размеры операндов, участвующих в арифметических операциях, разные? Например, предположим, что в операции сложения
74
один операнд является словом, а другой занимает двойное слово. Если числа без знака, то выход найти просто: можно на базе исходного операнда сформировать новый (формата двойного слова), старшие разряды которого просто заполнить нулями.
Аналогично преобразование беззнакового байтового значения в слово заключается просто в обнулении старшего байта слова. Например:
mov cl,12 mov al,cl mov ah,0
преобразуют беззнаковое значение 12 в регистре cl в значение 12 размером в слово в регистре ax.
Обратные преобразования двойного слова в слово или слова в байт тоже возможны (при определенных значениях!) Рассмотрим преобразование слова в байт. Например:
mov ax,5 mov bl,al
Здесь значение 5 размером в слово в регистре ax преобразуется в байтовое значение 5 в регистре bl. Естественно, что преобразуемое значение вместится в байт. Попытка преобразовать в байт значение 100h с помощью инструкций:
mov dx,100h mov al,dl
была бы безуспешной, так как в регистр al был бы записан только младший (нулевой) байт.
Для преобразования байтового значения со знаком в регистре al в слово со знаком существует инструкция cbw. Для преобразования слова со знаком в регистре ax в двойное слово со знаком в регистрах dx: ax (старшее слово содержится в регистре dx) предусмотрена специальная инструкция cwd (см. уч. пос., ч.4). Там же – про cdq (двойное из eax – в учетверенное в edx:eax) и cwde (слово из ax в двойное слово в eax).
4.70. Доступ к сегментным регистрам
Если одним из операндов инструкции mov является сегментный регистр, то другим операндом должен быть регистр общего назначения или ячейка памяти. Невозможно загрузить константу в сегментный регистр непосредственно, и непосредственно скопировать один сегментный регистр в другой сегментный регистр.
Вот, например, два способа установки регистра es в значение сегмента данных с именем
datasg segment para ‘Data’ ;сегмент данных
DataSeg dw datasg
75