Материал: 1117

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

sbb операнд_1,операнд_2 ; вычитание с учетом заема (флаг cf ):

;операнд_1 := операнд_1 – операнд_2 – значение_cf

Таким образом, среди команд вычитания есть команда sbb, учитывающая флаг переноса cf. Эта команда подобна adc, но теперь уже флаг cf выполняет роль индикатора заема 1 из старшего разряда при вычитании чисел.

Рассмотрим пример проверки заема при вычитании чисел без знака codesg segment para ‘Code’ ;сегмент кода

main:

;точка входа в программу

. . .

 

xor ax,ax

 

mov al,5

 

sub al,10

 

jnc m1

;нет переноса?

neg al

;в al модуль результата

m1:

 

. . .

 

exit:

 

mov ax,4c00h

;стандартный выход

int 21h

 

codesg ends

 

end main

;конец программы

В этом примере командой sub al,10 выполняется вычитание. С исходными данными, указанными для этой команды вычитания, результат получается в дополнительном коде (отрицательный). Чтобы получить модуль отрицательного результата, применяется команда neg. Тот факт, что на самом деле число отрицательное, отражен в состоянии флага cf.

4.61. Тридцатидвухразрядные операнды

Операции add и sub работают с 8- или 16-битовыми операндами. Чтобы сложить или вычесть 32-разрядные операнды, надо разбить

операцию на ряд операций со значениями размером в слово и использовать инструкции adc и sbb.

Когда складываются два операнда, флаг переноса cf показывает, был ли выполнен перенос из приемника. Сложение двух шестнадцатеричных значений:

f f f f +0001 10000

Младшее слово результата равно нулю, перенос равен 1, поскольку результат (10000h) не вмещается в 16 битов.

66

Инструкция adc аналогична инструкции add, но в ней учитывается флаг переноса, предварительно установленный предыдущим сложением. Когда складываются два значения, превышающие по размеру слово, то младшие (менее значащие) слова нужно сложить с помощью инструкции add, а остальные слова этих значений – с помощью одной или нескольких инструкций adc. Например, следующие инструкции складывают значение в регистрах cx:bx размером в двойное слово, со значением, записанным в регистрах dx:ax:

add

ax,bx

; ax:= ax + bx

adc

dx,cx

; dx := dx+ cx+<значение cf>,

а в следующей группе инструкций выполняется сложение учетверенного слова в переменной DoubleLong1 с учетверенным словом в переменной

DoubleLong2:

mov

ax, word ptr DoubleLong1

 

mov

bx,word ptr DoubleLong1+2

 

mov

cx,word ptr DoubleLong1+4

; (dx:cx:bx:ax) := учетверенное

mov

dx,word ptr DoubleLong1+6

; слово DoubleLong1

add

ax,word ptr DoubleLong2

 

adc

bx,word ptr DoubleLong2+2

 

adc

cx,word ptr DoubleLong1+4; (dx:cx:bx:ax) :=

adc

dx,word ptr DoubleLong2+6;

:= DoubleLong1 + DoubleLong2

Инструкция sbb работает по тому же принципу, что и инструкция adc. Когда инструкция sbb выполняет вычитание, в ней учитывается заем, произошедший в предыдущем вычитании. Например, следующие инструкции вычитают значение, записанное в регистрах cx:bx, из значения размером в двойное слово, записанного в регистрах dx:ax:

sub

ax,bx

; ax:= ax - bx

sbb

dx,cx

; dx := dx- cx-<значение cf>

4.62. Технология сложения и вычитания длинных чисел

Команды сложения и вычитания работают с операндами фиксированной размерности: 8 и 16 битов (процессор 8086) или 8, 16 и 32 бита (процессор 80386). А что делать, если нужно сложить числа большей размерности, например 48 или 64 бита, используя 16-разрядные операнды?

Процесс сложения многобайтных чисел происходит так же, как и при сложении двух чисел “в столбик”, — с осуществлением, при необходимости, переноса 1 в старший разряд. Т.к. удается запрограммировать этот процесс, то значительно расширяется диапазон двоичных чисел, над которыми можно выполнять операции сложения и вычитания.

67

Принцип вычитания чисел с диапазоном представления, превышающим стандартные разрядные сетки операндов, тот же, что и при сложении, то есть используется флаг переноса cf. Нужно только представлять себе процесс вычитания в столбик и правильно комбинировать команды микропроцессора с командой sbb.

В завершение обсуждения команд сложения и вычитания отметим, что кроме флагов cf и of в регистре flags есть еще несколько флагов, которые можно использовать с двоичными арифметическими командами. Речь идет о следующих флагах:

zf — флаг нуля, который устанавливается в 1, если результат операции равен нулю, и в 0, если результат не равен нулю;

sf — флаг знака, значение которого после арифметических операций (и не только) совпадает со значением старшего бита результата, то есть с битом 7, 15 или 31. Таким образом, этот флаг можно использовать для операций над числами со знаком.

4.63. Увеличение и уменьшение на единицу

Иногда в программе на ассемблере требуется выполнить сложение, которое состоит просто в прибавлении к операнду значения 1. Например, заполним 10-байтовый массив TempArray числами 0, 1, 2, 3, 4, 5, 6, 7, 8, 9:

datasg segment para ‘Data’

TempArray db

10 dup (?)

FillCount dw ?

 

datasg ends

 

codesg segment para ‘Code’

. . .

 

mov

al,0

; первое значение, записываемое в TempArray

mov

bx,offset TempArray ; bx указывает на TempArray

mov FillCount,10 ; число эл-тов, которыми нужно заполнить массив

FillTempArrayLoop:

mov

[bx],al

; установить текущий элемент TempArray

inc

bx

; ссылка на следующий элемент массива TempArray

inc

al

; следующее записываемое значение

dec

FillCount

; уменьшить счетчик числа заполняемых эл-тов

jnz FillTempArrayLoop ;обработать следующий эл-нт, если не все

. . .

codesg ends

Так как инструкция add занимает три байта, а инструкция inc занимает только один байт и выполняется быстрее, чем инструкция add, то предпочтительнее использовать инструкцию:

inc bx

; bx:= bx+1,

 

68

а не инструкцию:

 

add bx,1

; bx:= bx+1

Фактически более экономно выполнить две операции inc, чем прибавить к регистру размером в слово значение 2.

Аналогично иногда из содержимого регистров и переменных в памяти нужно вычесть значение 1. Тогда применяется инструкция dec.

Инструкции inc и dec – это наиболее эффективные инструкции, с помощью которых можно увеличивать и уменьшать значения переменных в памяти и регистров. Их следует использовать там, где это возможно.

4.64. Умножение целых чисел без знака

Процессор 8086 может выполнять следующие типы операций умножения: знаковое и беззнаковое (8-разрядное и 16-разрядное).

Инструкция mul перемножает беззнаковые сомножители.

При 8-битовом (8-разрядном) умножении один из операндов должен храниться в регистре al, а другой может представлять собой любой 8- битовый общий регистр или переменную памяти соответствующего размера. Инструкция mul всегда сохраняет 16-битовое произведение в регистре ax. Например, во фрагменте программы:

mov al,25 mov dh,40 mul dh

al умножается на dh, а результат (1000) помещается в регистр ax. Другой вариант использования этой инструкции, когда один из сомножителей хранится в ячейке памяти (например, в сегменте данных переменная b1 определена как байт: b1 db 40):

mov al,25

mul b1 ; ax :=al*b1=25*40=1000

В инструкции mul требуется указывать только один операнд, другой сомножитель всегда хранится в регистре al (или в регистре ax в случае перемножения 16-битовых сомножителей).

Инструкция перемножения 16-битовых сомножителей работает аналогично. Один из сомножителей должен храниться в регистре ax, а другой может находиться в 16-разрядном общем регистре или в переменной памяти. 32-битовое произведение инструкция mul помещает в этом случае в регистры dx:ax, при этом младшие (менее значащие) 16 битов произведения записываются в регистр ax, а старшие (более значащие) 16 битов – в регистр dx. Например, инструкции

mov ax,1000

mul ax ; (dx:ax):=ax*ax

69

загружают в регистр ax значение 1000, а затем возводят его в квадрат, помещая результат (значение 1 000 000) в регистры dx:ax.

Таким образом, для умножения чисел без знака предназначена команда

mul сомножитель_1

Как видите, в команде указан всего лишь один операнд-сомножитель. Второй операнд — сомножитель_2 задан неявно. Его местоположение фиксировано и зависит от размера сомножителей. Так как в общем случае результат умножения больше, чем любой из его сомножителей, то его размер и местоположение должны быть тоже определены однозначно. Варианты размеров сомножителей и размещения второго операнда и результата умножения для микропроцессора 8086 приведены в табл. 4.4.

Таблица 4.4

Сомножитель_1

Сомножитель_2

Результат

Байт

al

16 битов в ax:

 

 

al — младшая часть результата;

 

 

ah — старшая часть результата

Слово

ax

32 бита в паре dx:ax:

 

 

ax — младшая часть результата;

 

 

dx — старшая часть результата

Из таблицы видно, что произведение состоит из двух частей и в зависимости от размера операндов размещается в двух местах — на месте сомножитель_2 (младшая часть) и в дополнительном регистре ah, dx (старшая часть). Как же динамически (то есть во время выполнения программы) узнать, что результат достаточно мал и уместился в одном регистре или что он превысил размерность регистра и старшая часть оказалась в другом регистре? Для этого привлекаются уже известные нам флаги переноса cf и переполнения of:

–если старшая часть результата нулевая, то после операции произведения флаги cf = 0 и of = 0;

–если же эти флаги ненулевые, то это означает, что результат вышел за пределы младшей части произведения и состоит из двух частей, что и нужно учитывать при дальнейшей работе. Пример программы умножения:

stacksg segment para ‘Stack’ db 256 dup (0)

stacksg ends

datasg segment para ‘Data’

;сегмент данных

rez label word

 

rez_ldb 45

 

 

rez_h

db 0

 

datasg ends

 

 

codesg segment para ‘Code’

;сегмент кода

 

 

70

Источник: https://studfile.net/preview/16407174/