Регистры общего назначения

Восемь регистров общего назначения процессора (каждый размером 16 битов) используются в операциях большинства инструкций в качестве источника или приемника при перемещении данных и вычислениях, указателей на ячейки памяти и счетчиков. Каждый регистр общего назначения может использоваться для хранения 16-битового значения, в арифметических и логических операциях, может выполняться обмен между регистром и памятью (запись из регистра в память и наоборот). Например, в данном фрагмента программы:

mov ax,5

mov dx,9

add ax,dx

значение 5 загружается в регистр AX, значение 9 - в DX, и эти два значения складываются вместе. При этом результат (14) сохраняется в регистре AX. Вместо регистров AX и DX здесь можно использовать регистр CX, SI или любой другой регистр общего назначения.

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

Регистр AX называют также накопителем (аккумулятором). Этот регистр всегда используется в операциях умножения или деления и является также одним из тех регистров, который можно использовать для наиболее эффективных операций (арифметических, логических или операций перемещения данных).

Младшие 8 битов регистра AX называются также регистром AL, а старшие 8 битов - регистром AH. Это может оказаться удобным при работе с данными размером в байт. Таким образом, регистр AX можно использовать, как два отдельных регистра. В следующем фрагменте программы регистр AH устанавливается в значение 0, это значение копируется в AL и затем в регистр AL добавляется 1.

mov ah,0

mov al,ah

inc al

В результате в регистре AX будет записано значение 1. Регистры BX, CX и DX могут аналогичным образом использоваться либо как один 16-разрядный регистр, либо как два 8-разрядных.

Регистр BX может использоваться для ссылки на ячейку памяти (указатель). 16-битовое значение, записанное в BX, может использоваться в качестве части адреса ячейки памяти, к которой производится доступ. Например, следующий код загружает в AL содержимое адреса памяти 9:

mov ax,0

mov ds,ax

mov bx,9

mov al,[bx]

Перед обращением к ячейке памяти, на которую указывает BX, мы загрузили в DS значение 0 (через регистр AX). Это результат сегментной организации памяти процессора. По умолчанию, когда BX используется в качестве указателя на ячейку памяти, он ссылается на нее относительно сегментного регистра DS.

Как и регистры AX, CX и DX, регистр BX может интерпретироваться, как два восьмибитовых регистра - BH и BL.

Специализация регистра CX - использование в качестве счетчика. Предположим, мы хотим 10 раз повторить выполнение блока инструкций. Это можно сделать следующим образом:

mov cx,10

Begin:

<блок инструкций, который нужно повторить>

sub cx,1

jnz Begin

Инструкции между меткой Begin и инструкцией JNZ будут повторяться до тех пор, пока содержимое CX не станет равным 0. Чтобы уменьшить содержимое CX и перейти на начало цикла Begin, если CX еще не равен 0, используются две инструкции - SUB CX,1 и JNZ.

Уменьшение значения счетчика и цикл - это часто используемый элемент программы, поэтому в процессоре 8086 используется специальная инструкция для того, чтобы циклы выполнялись быстрее и были более компактными. Эта инструкция называется LOOP. Инструкция LOOP (цикл) вычитает 1 из CX и выполняет переход, если содержимое регистра CX не равно 0 (все это в одной инструкции). Для приведенного выше примера можно записать такой эквивалент:

mov cx,10

Begin

<блок инструкций, который нужно повторить>

loop Begin

Регистр CX особенно полезен для использования в циклах и в качестве счетчика. Как и регистры AX, BX и DX, регистр CX можно интерпретировать, как два 8-разрядных регистра - CH и CL.

Регистр DX - это единственный регистр, которые может использоваться в качестве указателя адреса ввода-вывода в инструкциях IN и OUT. Фактически, кроме использования регистра DX нет другого способа адресоваться к портам ввода-вывода с 256 по 65535. Например, в следующем фрагменте программы в порт 1000 записывается значение 62:

mov al,62

mov dx,1000

out dx,al

Другие уникальные качества регистра DX относятся к операциям деления и умножения. Когда делится 32-битовое делимое на 16-битовый делитель, старшие 16 битов делимого должны быть помещены в регистр DX. После выполнения деления остаток также сохраняется в DX. Младшие 16 битов делимого должны быть помещены в AX. Частное от деления также будет записано в AX.. Аналогично, когда перемножаются два 16-битовых сомножителя, старшие 16 битов произведения сохраняются в DX (младшие 16 битов записываются в регистр AX).

Как и регистры AX, BX и DX, регистр DX можно интерпретировать, как два 8-разрядных регистра - DH и DL.

Индексные регистры

Как и регистр BX, регистр SI может использоваться, как ука-

затель на ячейку памяти. Например:

mov ax,0

mov ds,ax

mov si,20

mov al,[si]

Здесь 8-битовое значение, содержащееся по адресу 20, записывается в регистр AL. Особенно полезно использовать регистр SI для ссылки на память в строковых инструкциях процессора. Например:

mov ax,0

mov ds,ax

mov si,20

mov al,[si]

lodsb

Здесь не только содержимое по адресу памяти, на который указывает SI, сохраняется в AX, но к SI также добавляется 1. Это может оказаться очень эффективным при организации доступа к последовательным ячейкам памяти (например, к строке текста). Кроме того, можно сделать так, что строковые инструкции будут автоматически определенное число раз повторять свои действия, так что отдельная инструкция может выполнить сотни, а иногда и тысячи действий.

Регистр DI очень похож на регистр SI в том плане, что его

можно использовать в качестве указателя ячейки памяти. При использовании его в строковых инструкциях он имеет также особые свойства. Например:

mov ax,0

mov ds,ax

mov di,1024

add bl,[di]

lodsb

Здесь 8-битовое значение, расположенное по адресу 1024, за-

писывается в регистр BL. При использовании его в строковых инструкциях регистр DI несколько отличается от регистра SI. В то время как SI всегда используется в строковый инструкциях, как указатель на исходную ячейку памяти (источник), DI всегда служит указателем на целевую ячейку памяти (приемник). Кроме того, в строковых инструкциях регистр SI обычно адресуется к памяти относительно сегментного регистра DS, в то время как DI всегда адресуется к памяти относительно сегментного регистра ES. (Когда SI и DI используются в качестве указателей на ячейки памяти в других инструкциях (не строковых), то они всегда адресуются к памяти относительно регистра DS.) Например:

cld

mov dx,0

mov es,dx

mov di,2048

stosb

Строковая инструкция STOSB используется здесь и для сохранения значения в AL (по адресу памяти, на который указывает регистр DI), и для добавления к DI 1.

Регистры - указатели

Как и регистры BX, SI и DI, регистр BP также может использоваться в качестве указателя на ячейку памяти, но здесь есть некоторые отличия. Регистры BX, SI и DI обычно ссылаются на память относительно сегментного регистра DS (или, в случае использования в строковых инструкциях регистра DI, относительно сегментного регистра ES), а регистр BP адресуется к памяти относительно регистра SS (сегментный регистр стека).

Стек находится в сегменте, на который указывает регистр SS.

Например:

push bp

mov bp,sp

mov ax,[bp+4]

Здесь выполняется обращение к сегменту стека для загрузки в AX первого параметра. Регистр BP создан для обеспечения работы с параметрами, локальными переменными другой адресации к памяти с использованием стека.

Регистр SP называется также указателем стека. Это "наименее “общий” из регистров общего назначения, поскольку он практически всегда используется для специальной цели - обеспечения стека. Стек - это область памяти, в которой можно сохранять значения и из которой они могут затем извлекаться по принципу "последний пришел – первый ушел" (FIFO). То есть последнее сохраненное в стеке значение будет первым значением, которое вы получите при чтении из стека.

Регистр SP в каждый момент времени указывает на вершину стека. Вершина стека - это то место, в котором в стеке сохраняется следующее помещенное туда значение. Действие, состоящее в занесении значений в стек, называют также "заталкиванием" (pushing) в стек. В самом деле, инструкция PUSH используется для занесения значений в стек. Аналогично, действие, состоящее в извлечении (выборке) значений из стека, называют так же "выталкиванием" (popping) из стека (для этого используется инструкция POP).

Начальное значение SP равно 1000:

mov ax,1

push ax

mov bx,2

push bx

pop ax

pop bx

Хотя процессор и позволяет записывать значения в SP или складывать и вычитать хранящиеся в регистре SP значения (как это можно делать с обычными регистрами общего назначения), к этому не следует к этому прибегать.

Занесение в стек и извлечение из него не является единственным способом использования стека. Стек используется всякий раз, когда происходит вызов или возврат из подпрограммы (процедуры или функции). Кроме того, стек используют некоторые системные ресурсы (такие, как клавиатура или системный таймер), когда они прерывают процессор, чтобы выполнить свои функции. Все это означает, что стек может в любой момент потребоваться. Если вы измените SP, даже на несколько инструкций, то правильное значение стека может оказаться недоступным, когда он потребуется системным ресурсам.

Указатель инструкций

Указатель инструкций (регистр IP) всегда содержит смещение в памяти, по которому хранится следующая выполняемая инструкция. Когда выполняется одна инструкция, указатель инструкций перемещается таким образом, чтобы указывать на адрес памяти, где хранится следующая инструкция. Обычно следующей выполняемой инструкцией является инструкция, хранимая по следующему адресу памяти, но некоторые инструкции, такие, как вызовы или переходы, могут привести к тому, что в указатель инструкций будет загружено новое значение. Таким образом, будет выполнен переход на другой участок программы.

Значение счетчика инструкций нельзя прочитать или записать непосредственно. Загрузить в указатель инструкций новое значение может только специальная инструкция перехода (аналогичная только что описанным).

Указатель инструкций сам по себе не определяет адрес, по которому находится следующая выполняемая инструкция. Для извлечения инструкции предусмотрен регистр CS, где хранится базовый адрес, при этом указатель инструкций задает смещение относительно этого базового адреса.

Сегментные регистры

Основной предпосылкой сегментации является следующее: процессор может адресоваться к 1 мегабайту памяти. Для адресации ко всем ячейкам адресного пространства в 1 мегабайт необходимы 20-разрядные сегментные регистры. Однако процессор использует только 16-разрядные указатели на ячейки памяти. Для ссылки на память используется 16-разрядный регистр BX. Как же тогда согласовать 16-разрядные указатели процессора и 20-разрядные адреса? Ответ состоит в том, что процессор использует двухступенчатую схему адресации. Каждый 16-разрядный указатель памяти или смещение комбинируется с содержимым 16-разрядного сегментного регистра для формирования 20-разрядного адреса памяти.

Сегменты и смещения комбинируются следующим образом: значение сегмента сдвигается влево на 4 (то есть умножается на 16), а затем складывается со смещением

mov ax,1000h

mov ds,ax

mov si,201h

mov dl,[si]

Здесь для сегментного регистра DS устанавливается значение 1000h, SI устанавливается в значение 201h. Можно представить их в виде “сегмент:смещение” - 1000:201h. (Эффективные вычисления для пары "сегмент:смещение" могут выполняться только по основанию 16.

Теперь можно видеть, что программа получает доступ к полному адресному пространству в 1 мегабайт с помощью использования только пары "сегмент:смещение". Все инструкции и режимы адресации процессора по умолчанию работают относительно того или иного сегментного регистра, хотя в некоторых инструкциях можно явно указать, что нужно использовать желаемый сегментный регистр.

Наиболее общим именем сегмента является @Datа, которое в упрощенных директивах определения сегментов используется для ссылки на используемый по умолчанию сегмент данных. Например:

.MODEL SMALL

.DATA

var1 DW 0

.CODE

mov ax,@data

mov ds,ax

END

Здесь регистр DS загружается таким образом, что он будет указывать на используемый по умолчанию сегмент данных, в котором находится Var1.

Использование сегментов процессора 8086 приводит к некоторым интересным моментам. Один из них состоит в том, что только блок памяти размером в 64 Кб в любой момент может адресоваться через сегментный регистр, так как 64 Кб - это максимальный объем памяти, к которой можно адресоваться с помощью 16-битового смещения. Это может оказаться неудобным при работе с большим (более 64 Кб) объемом памяти, так как и значение сегментного регистра, и смещение, придется часто изменять.

Адресация к большим блокам памяти в процессоре может представлять еще большую трудность, поскольку, в отличие от регистров общего назначения (общих регистров), сегментные регистры не могут использоваться в качестве источников или приемников в арифметических и логических инструкциях. Фактически, единственная операция, которую можно выполнять с сегментными регистрами, состоит в копировании значений между сегментными регистрами и другими общими регистрами или памятью. Например, чтобы добавить значение 100 к регистру ES, потребуется следующее:

mov ax,es

add ax,100

mov es,ax

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

Второй момент использования сегментов состоит в том, что

каждая ячейка памяти адресуется через многие возможные сочетания "сегмент:смещение". Например, адрес памяти 100h адресуется с помощью следующих значений "сегмент:смещение": 0:100h, 1:F0h, 2:E0h и т.д., так как при вычислении всех этих пар "сегмент:смещение" получается значение адреса 100h.

Аналогично регистрам общего назначения каждый сегментный регистр играет свою, конкретную роль. Регистр CS указывает на код программы, DS указывает на данные, SS - на стек, сегмент (сегментный регистр) ES - это дополнительный сегмент, который может использоваться так, как это необходимо.

Регистр CS указывает на начало блока памяти объемом 64К, или сегмент кода, в котором находится следующая выполняемая инструкция. Следующая инструкция, которую нужно выполнить, находится по смещению, определяемому в сегменте кода регистром IP, то есть на нее указывает адрес (в форме "сегмент:смещение") CS:IP. Процессор никогда не может извлечь инструкцию из сегмента, отличного от того, который определяется регистром CS.

Регистр DS указывает на начало сегмента данных, которые

представляет собой блок памяти объемом 64 Кб, в котором находится большинство размещенных в памяти операндов. Обычно для ссылки на адреса памяти используются смещения, предполагающие использование регистров BX, SI или DI. В основном сегмент данных представляет собой то, о чем говорит его название: как правило это сегмент, в котором находится текущий набор данных.

Регистр ES указывает на начало блока памяти объемом 64 Кб, который называется дополнительным сегментом. Как и подразумевает его название, дополнительный сегмент не служит для какой-то конкретной цели, но доступен тогда, когда в нем возникает необходимость. Иногда дополнительный сегмент используется для выделения дополнительного блока памяти объемом 64 Кб для данных. Доступ к памяти в дополнительном сегменте менее эффективен, чем доступ к памяти в сегменте данных.

Особенно полезен дополнительный сегмент, когда используются строковые инструкции. Все строковые инструкции, которые выполняют запись в память, используют в качестве адреса памяти, в которую нужно выполнить запись, пару регистров ES:DI. Это означает, что регистр ES особенно полезен при использовании его в качестве целевого сегмента при копировании блоков, сравнении строк, просмотре памяти и очистке блоков памяти.

Регистр SS указывает на начало сегмента стека, которые представляет собой блок памяти объемом 64К, в котором находится стек. Все инструкции, которые неявно используют регистр SP (включая занесение в стек, извлечение из стека, вызовы и возвраты управления), работают с сегментом стека, так как только регистр SP может использоваться для адресации памяти в сегменте стека.

Регистр BP также работает относительно сегмента стека. Это позволяет использовать регистр BP для доступа к параметрам и переменным, которые хранятся в стеке

Ввод информации с клавиатуры - один из основных способов взаимодействия с IBM PC. DOS обеспечивает рад функций, с помощью которых программа на ассемблере может обрабатывать нажатия клавиш. Одним из наиболее простых способов получения символов клавиш является функция "Ввод с клавиатуры", то есть функция DOS номер 1. Функции DOS вызываются путем помещения номера функции в регистр AH и выполнения затем инструкции INT 21h. Следующий набранный на клавиатуре символ возвращается в регистре AL. Например, когда выполняется код:

mov ah,1

int 21h

DOS помещает следующий набранный на клавиатуре символ в AL. Если клавиша не нажата, DOS будет ждать, когда она будет нажата, поэтому для выполнения данной функции может потребоваться неопределенное время.

ВЫВОД СИМВОЛОВ НА ЭКРАН

Функция DOS с номером 2 обеспечивает наиболее непосредственный путь вывода символа на экран. Для этого нужно просто поместить 2 в регистр AH и выводимый символ в регистр DL, а затем вызвать DOS с помощью INT 21h. Следующий код отображает каждый введенный символ на экране:

mov ah,1

int 21h получить следующую нажатую клавишу

mov ah,2

mov dl,al ; переместить считанный символ из AL в DL

int 21h ; вывести его на экран

Имеется также ряд других функций для считывания и вывода символов и строк символов. Можно привести пример простой программы, которая выполняет эхо-отображения строки набранных на клавиатуре символов на экране.

Для завершения программы имеется несколько функций DOS, но наиболее предпочтительным методом является выполнение функции DOS с номером 4Ch (или 76 для тех, кто предпочитает десятичный вид). Зная это, можно теперь написать полную программу отображения символов:

.MODEL SMALL

.STACK 100h

.DATA

.CODE

EhcoLoop:

mov ah,1 ; функция DOS ввода с клавиатуры

int 21h ; получить следующую клавишу

cmp al,13 ; это клавиша ENTER?

jz EchoDone ; да, выполняем эхоотображение

mov dl,al ; поместить символ в DL

mov ah,2 ; функция DOS вывода на экран

int 21h ; вывести на экран символ

jnz EchoLoop ; отобразить следующий символ

EchoDone:

mov ah,4ch ; функция DOS завершения программы

int 21h ; завершить программу

END

МЕТКИ

Метки - имена, использующиеся в программе для ссылки на числа и строки символов или ячейки памяти. Метки позволяют вам присваивать имена переменным в памяти, значениям и адресам, где находятся конкретные инструкции. Например, в следующей программе, которая вычисляет факториал 5, используется несколько меток:

.MODEL SMALL

.STACK 200h

.DATA

FactorialValue DW ?

Factorial DW ?

.CODE

FiveFactorial PROC

mov ax,@Data

mov ds,ax

mov [FactorialValue],1

mov [Factorial],2

mov cx,4

FiveFactorialLoop:

mov ax,[FactorialValue]

mul [Factorial]

mov [FactorialValue],ax

inc [Factorial]

loop FiveFactorialLoop

ret

FiveFactorial ENDP

END

Метки FactorialValue и Factorial эквивалентны адресам двух 16-битовых переменных. Они используются для последующей ссылки в программе на эти две переменные. Метка FiveFactorial - это имя подпрограммы (процедуры или функции), содержащей код. Она позволяет вызывать этот код в других частях программы. Наконец, метка FiveFactorialLoop эквивалентна адресу инструкции:

mov ax,[FactorialValue],

благодаря которой оператор LOOP в конце программы может осуществлять обратный переход на эту инструкцию.

ОПЕРАНДЫ

Мнемоники инструкций и директивы сообщают Ассемблеру, что нужно делать. С другой стороны, операнды указывают Ассемблеру, какие регистры, параметры, ячейки памяти и т.д. нужно связать с каждым вхождением инструкции или директивы. Инструкция MOV (перемещение данных) сама по себе ничего не означает. Чтобы указать Ассемблеру, откуда нужно извлечь перемещаемое значение и где его сохранить, необходимы операнды.

Для различных инструкций требуются 0, 1, 2 или более операндов. В действительности различными директивами может восприниматься любое число операндов, которое может уместиться на одной строке. Правильное число операндов зависит от конкретной инструкции или директивы. (В общем случае допускается три операнда.) Возможные операнды включают в себя регистры, константы, метки, переменные в памяти и текстовые строки. Когда процессор 8086 выполняет инструкцию:

mov ax,bx

инструкция MOV помещает содержимое BX в AX.

Регистровые операнды

Регистровые операнды являются наиболее часто используемыми в инструкциях операндами. Регистры могут использоваться в качестве источника (исходный операнд) или приемника (целевой операнд) и при некоторых обстоятельствах могут даже содержать адрес, на который нужно выполнить переход. С регистрами можно делать много того, чего нельзя делать с константами, метками или переменными в памяти. С другой стороны, имеются некоторые инструкции, в которых можно использовать только регистровые операнды.

Примеры регистровых операндов:

mov al,ax

push dl

xchg al,dl

ror dx,cl

in al,dx

inc sl

Регистровые операнды могут использоваться вместе с другими операндами:

mov al,1

add [BaseCount],cx

cmp si,[bx]

Использование регистровых операндов не требует обширных пояснений. Чтобы использовать регистр в качестве операнда, вы задаете имя этого регистра и использующую регистр инструкцию. Если имеется два операнда и регистровым операндом является самый правый операнд, то он будет исходным регистром (источником), а если самым левым операндом - то это целевой регистр (приемник). Если в инструкции требуется два источника, то может присутствовать еще один исходный регистр. Например, во фрагменте программы:

mov cx,1

mov dx,2

sub dx,cx

регистр CX устанавливается в значение 1, DX - в значение 2, а за-

тем из DX вычитается CX и результат (1) снова записывается в DX. В инструкции SUB CX является правым операндом, поэтому это исходный регистр (источник). DX - самый левый операнд, поэтому он одновременно является вторым источником и приемником. Действие данной инструкции SUB (вычитание) выражается словами, как "вычесть CX из DX".

Постоянные операнды

Регистры удобны для хранения значений переменных, но часто в операндах требуется использовать постоянное значение. Если необходимо в цикле уменьшать значение регистра SI на 4, повторяя цикл, пока значение SI не станет равным 0, можно использовать следующие операторы:

CountByFourLoop:

dec si

dec si

dec si

dec si

jnz CountByFourLoop

Однако намного проще использовать операторы:

CountByFourLoop:

sub si,1

jnz CountByFourLoop

В качестве постоянных операндов (операндов-констант) можно использовать также символы, поскольку символ представляет собой определенное значение. Например, так как символ A имеет десятичное значение 65, то следующие две инструкции эквивалентны:

sub al,'A'

sub al,65

Постоянные значения можно задавать в двоичном, восьмеричном или шестнадцатиричном представлении, а также в десятичном виде.

Операнды-константы никогда не могут при использовании двух операндов располагаться слева, так как невозможно использовать константу в качестве операнда-приемника (это противоречит определению константы, как неизменяемой величины). Операнды-константы, однако, могут прекрасно использоваться в том месте, где имеет смысл использование значения в качестве исходного операнда. Чтобы занести в стек значение 5, вы должны выполнить две инструкции:

mov ax,5

push ax

РАБОТА СО СТЕКОМ

На вершину стека всегда указывает регистр SP. Для обращения к данным в стеке с использованием режимов адресации памяти, при которых в указателем базы является регистр BP, можно использовать инструкцию MOV. Например, инструкция:

mov ax,[bp+4]

загружает регистр AX содержимым слова в сегменте стека со смещением BP+4.

Однако чаще к стеку обращаются с помощью инструкций PUSH и POP. Инструкция PUSH сохраняет операнд в вершине стека, а инструкция POP извлекает значение из вершины стека и сохраняет его в операнде. Например, инструкции:

mov ax,1

push ax

pop bx

заносят значение (равное 1) в регистре AX в вершину стека, затем извлекают 1 из вершины стека и сохраняют ее в BX.

АРИФМЕТИЧЕСКИЕ ОПЕРАЦИИ

Выполнять обмен содержимого двух операндов позволяет инструкция XCHG. Это предоставляет удобный способ выполнять операцию, которая в противном случае потребовала бы трех инструкций. Например, инструкция:

xchg ax,dx

выполняет обмен содержимого AX и DX, что эквивалентно выполнению инструкций:

push ax

mov ax,dx

pop dx

Операции ADD и SUB работают с 8- или 16-битовыми операндами. Если нужно сложить или вычесть 32-разрядные операнды, надо разбить операцию на ряд операций со значениями размером в слово и использовать инструкции ADC и SBB.

Когда складываются два операнда, процессор записывает состояние во флаг переноса (бит С в регистре флагов), которое показывает, был ли выполнен перенос из приемника. Младшее слово результата равно нулю, перенос равен 1, поскольку результат (10000h) не вмещается в 16 битов. Инструкция ADC аналогична инструкции ADD, но в ней учитывается флаг переноса (предварительно установленный предыдущим сложением). Всякий раз когда складываются два значения, превышающие по размеру слово, то младшие (менее значащие) слова нужно сложить с помощью инструкции ADD, а остальные слова этих значений - с помощью одной или нескольких инструкций ADC, последними складывая самые значащие слова. Например, следующие инструкции складывают значение в регистрах CX:BX, размером в двойное слово, со значением, записанным в регистрах DX:AX:

add ax,bx

adc dx,cx

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

mov ax,[DoubleLong1]

add [DoubleLong2],ax

mov ax,[DoubleLong1+2]

adc [DoubleLong2+2],ax

mov ax,[DoubleLong1+4]

adc [DoubleLong1+4],ax

mov ax,[DoubleLong1+6]

adc [DoubleLong2+6],ax

Инструкция SBB работает по тому же принципу, что и инструкция ADC. Когда инструкция SBB выполняет вычитание, в ней учитывается заем, произошедший в предыдущем вычитании. Например, следующие инструкции вычитают значение, записанное в регистрах CX:BX, из значения размеров в двойное слово, записанного в регистрах DX:AX:

sub ax,bx

sbb dx,cx

При работе с инструкциями ADC и SBB нужно убедиться, что флаг переноса не изменился с момента выполнения последнего сложения или вычитания, иначе состояние заема/переноса, хранящееся во флаге переноса, будет потеряно. Например, в следующем фрагменте программы сложение CX:BX с DX:AX выполняется некорректно:

add ax,bx ; сложить младшие слова

sub si,si ; очистить SI (флаг переноса сбрасывается в 0)

adc dx,cx ; сложить старшие слова...

; это будет работать некорректно,

; так как с момента последней

; операции сложения содержимое

; флага переноса потеряно

Процессор 8086 может выполнять отдельные типы операций умножения и деления. Эта одна из сильных сторон процессора, поскольку во многих микропроцессорах вообще отсутствует непосредственная поддержка операций умножения и деления, а эти операции довольно сложно выполнить программным путем.

Инструкция MUL перемножает 8- или 16-битовые беззнаковые сомножители, создавая 16- или 32-битовое произведение.

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

mov al,25

mov dh,40

mul dh

AL умножается на DH, а результат (1000) помещается в регистр AX.

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

mov ax,1000

mul ax

загружают в регистр AX значение 1000, а затем возводят его в

квадрат, помещая результат (значение 1000000) в регистры DX:AX.

В отличие от сложения и вычитания, в операции умножения не учитывается, являются ли сомножители операндами со знаком или без знака, поэтому имеется вторая инструкция умножения IMUL для умножения 8- и 16-битовых сомножителей со знаком. Если не принимать во внимание, что перемножаются значения со знаком, инструкция IMUL работает аналогично инструкции MUL. Например, при выполнении инструкций:

mov al,-2

mov ah,10

imul ah

в регистре AX будет записано значение -20.

Процессор позволяет с определенными ограничениями разделить 32-битовое значение на 16-битовое, или 16-битовое значение на 8-битовое. Сначала рассмотрим деление 16-битового значения на 8-битовое.

При беззнаковом делении 16-битового значения на 8-битовое делимое должно быть записано в регистре AX. 8-битовый делитель может храниться в любом 8-битовом общем регистре или переменной в памяти соответствующего размера. Инструкция DIV всегда записывает 8-битовое частное в регистр AL, а 8-битовый остаток - в AH. Например, в результате выполнения инструкций:

mov ax,51

mov dl,10

div dl

результат 5 (51/10) будет записан в AL, а остаток 1 (остаток от

деления 51/10) - в AH. Частное представляет собой 8-битовое значение. Это означает, что результат деления 16-битового операнда на 8-битовый операнд должен превышать 255. Если частное слишком велико, то генерируется прерывание 0 (прерывания по делению на 0). Инструкции:

mov ax,0fffh

mov bl,1

div bl

генерируют прерывание по делению на 0 (как можно ожидать, прерывание по делению на 0 генерируется также, если 0 используется в качестве делителя).

При делении 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. Например, операции:

.DATA

TestDivisor DW 100

.CODE

mov ax,-667

cwd ; установить DX:AX в значение -667

idiv [TestDivisor]

сохраняют значение -6 в регистре AX и значение -67 в регистре DX.

Наши рекомендации