6.2. Команды арифметического вычитания SUB и SBB
Команда вычитания SUB – идентична команде сложения, за исключением того, что она выполняет вычитание, а не сложение. Для нее верны предыдущие схемы, если в них поменять знак «+» на «–», т. е. она из первого операнда вычитает второй и помещает результат на место первого операнда. Команда вычитания также устанавливает флаги состояния в соответствии с результатом операции (флаг переноса здесь трактуется как заем). Команда вычитания с заемом SBB учитывает флаг заема CF, то есть значение заема вычитается из младшего бита результата.
Примеры использования команд SUB и SBB:
SUB AL,12h
SUB Count,1
SBB BX,4
SUB AX,BX
SBB Count,DI
6.3. Команда смены знака NEG
Команда отрицания NEG – оператор смены знака. Она меняет знак двоичного кода операнда – байта или слова.
6.4. Команды инкремента INC и декремента DEC
Команды инкремента и декремента изменяют значение своего единственного операнда на единицу. Команда INC прибавляет 1 к операнду, а команда DEC вычитает 1 из операнда. Обе команды могут работать с байтами или со словами. На флаги команды влияния не оказывают.
6.5. Команды умножения MUL и IMUL
Существуют две формы команды умножения. По команде MUL умножаются два целых числа без знака, при этом результат тоже не имеет знака. По команде IMUL умножаются целые числа со знаком. Обе команды работают с байтами и со словами, но для этих команд диапазон форм представления гораздо уже, чем для команд сложения и вычитания. На приведенных ниже рисунках представлены все варианты команд умножения.
При умножении 8-битовых операндов результат всегда помещается в регистр AX. При умножении 16-битовых данных результат, который может быть длиною до 32 бит, помещается в пару регистров: в регистре DX содержатся старшие 16-бит, а в регистре AX – младшие 16-бит. Умножение не допускает непосредственного операнда.
Установка флагов командой умножения отличается от других арифметических команд. Единственно имеющие смысл флаги – это флаг переноса и переполнения.
Команда MUL устанавливает оба флага, если старшая половина результата не нулевая. Если умножаются два байта, установка флагов переполнения и переноса показывает, что результат умножения больше 255 и не может содержаться в одном байте. В случае умножения слов флаги устанавливаются, если результат больше 65535.
Команда IMUL устанавливает флаги по тому же принципу, т. е. если произведение не может быть представлено в младшей половине результата, но только в том случае если старшая часть результата не является расширением знака младшей. Это означает, что если результат положителен, флаг устанавливается как в случае команды MUL. Если результат отрицателен, то флаги устанавливаются в случае, если не все биты кроме старшего, равны 1. Например, умножение байт с отрицательным результатом устанавливает флаги, если результат меньше 128.
Примеры использования команд умножения:
MUL CX
IMUL Width
6.6. Команды деления DIV и IDIV
Как и в случае умножения, существуют две формы деления – одна для двоичных чисел без знака DIV, а вторая для чисел со знаком – IDIV. Любая форма деления может работать с байтами и словами. Один из операндов (делимое) всегда в два раза длиннее обычного операнда. Ниже приведены схемы, иллюстрирующие команды деления.
Байтовая команда делит 16-битовое делимое на 8-битовый делитель. В результате деления получается два числа: частное помещается в регистр AL, а остаток – в AH. Команда, работающая со словами, делит 32-битовое делимое на 16-битовый делитель. Делимое находится в паре регистров DX:AX, причем регистр DX содержит старшую значимую часть, а регистр AX – младшую. Команда деления помещает частное в регистр AX, а остаток в DX.
Ни один из флагов состояния не определен после команды деления. Однако, если частное больше того, что может быть помещено в регистр результата (255 для байтового деления и 65535 для деления слов), возникает ошибка значимости и выполняется программное прерывание уровня 0.
Примеры использования команд деления:
IDIV CX
DIV Count
Рассмотрим пример программы, использующей большинство из описанных выше команд.
Пример. Вычислить значение арифметического выражения. Все числа являются 16-битовыми целыми со знаком. Формула вычислений следующая:
Эту задачу решает приведенная ниже программа.
;Сегмент стека
SSEG SEGMENT STACK
DB 256 DUP(?)
SSEG ENDS
;Сегмент данных
DATA SEGMENT
X DW ? ;Память для переменных
A DW ?
B DW ?
C DW ?
D DW ?
DATA ENDS
;Сегмент кода
CODE SEGMENT
ASSUME CS:CODE, DS:DATA, SS:SSEG
START:
MOV AX,Data ;Инициализация DS
MOV DS,AX
;Вычислительная часть
MOV AX,2 ;Загрузка константы
IMUL A ;dx:ax = a*2
MOV BX,DX
MOV CX,AX ;bx:cx = a*2
MOV AX,B
IMUL C ;dx:ax = b*c
ADD AX,CX
ADC DX,BX ;dx:ax = a*2+b*c
MOV CX,D
SUB CX,3 ;cx = d-3
IDIV CX ;ax = (a*2+b*c)/(d-3)
NEG AX ;ax = -ax
INC AX ;ax = ax+1
MOV X,AX ;Сохранение результата
MOV AH,4CH
INT 21H
CODE ENDS
END START
На первом этапе программа выполняет два умножения. Так как результат умножения всегда помещается в пару регистров DX:AX, то в примере результат первого умножения переносится в пару регистров BX:CX перед выполнением второго умножения. Затем программа выполняет сложение числителя. Поскольку умножение дает 32-битовые результаты, в программе требуется сложение повышенной точности (с учетом флага переноса). После сложения результат остается в DX:AX (числитель). Знаменатель вычисляется в регистре CX, а затем на него делится числитель. Частное записывается в регистр AX, затем его знак меняется на обратный и к полученному значению прибавляется 1. На последнем этапе программа записывает результат из регистра AX в переменную X. Остаток игнорируется.
7. Команды побитовой обработки
Эту группу команд можно разделить на две подгруппы: логические операции и операции сдвигов. Команды AND, TEST, OR, XOR и команды сдвигов изменяют значения флагов CF, OF, PF, SF, ZF (значение флага AF становится неопределенным). Команды циклических сдвигов изменяют только флаги OF и CF.
7.1. Команды, выполняющие логические операции
К командам, выполняющим логические операции, относятся AND, OR и XOR. Указанные команды выполняют соответственно операции «логическое умножение» (конъюнкцию), «логическое сложение» (дизъюнкцию) и «исключающее или» для двух операндов и помещают результат на место первого операнда. К группе логических команд также относится команда TEST, которая производит те же действия, что и команда AND, но не изменяет своих операндов, а лишь устанавливает соответствующие флаги.
В качестве операндов логических команд могут выступать те же операнды, что и у команд сложения и вычитания.
Примеры использования логических команд:
AND BL,100
TEST CX,DX
OR DX,Mask
XOR Flag,1000b
7.2. Команды, выполняющие операции сдвигов
Команды сдвига перемещает все биты в поле данных либо вправо, либо влево, работая либо с байтами, либо со словами. Каждая команда содержит два операнда: первый операнд – поле данных – может быть либо регистром, либо ячейкой памяти; второй операнд – счетчик сдвигов. Его значение может быть равным 1, или быть произвольным. В последнем случае это значение необходимо занести в регистр CL, который указывается в команде сдвига. Число в CL может быть в пределах 0-255, но его практически имеющие смысл значения лежат в пределах 0-16.
Общая черта всех команд сдвига – установка флага переноса. Бит, попадающий за пределы операнда, сохраняется во флаге переноса. Всего существует 8 команд сдвига: 4 команды обычного сдвига и 4 команды циклического сдвига. Команды циклического сдвига переносят появляющийся в конце операнда бит в другой конец, а в случае обычного сдвига этот бит пропадает. Значение, вдвигаемое в операнд, зависит от типа сдвига. При логическом сдвиге вдвигаемый бит всегда 0, арифметический сдвиг выбирает вдвигаемый бит таким образом, чтобы сохранить знак операнда. Команды циклического сдвига с переносом и без него отличаются трактовкой флага переноса. Первые рассматривают его как дополнительный 9-ый или 17-ый бит в операции сдвига, а вторые нет.
Ниже приведен перечень команд сдвига:
команды логического сдвига вправо SHR и влево SHL;
команды арифметического сдвига вправо SAR и влево SAL;
команды циклического сдвига вправо ROR и влево ROL;
команды циклического сдвига вправо RCR и влево RCL с переносом;
Действие команд сдвига иллюстрируют следующие рисунки.
Рис. 7.1. Команды сдвига.
Примеры использования команд сдвига:
SHL CH,1
SHL [BP],CL
RCL Size,1
Приведенная ниже программа иллюстрирует использование команд побитовой обработки.
Пример. Вывести на экран шестнадцатеричное представление кода символа «Q».
;Сегмент стека
SSEG SEGMENT STACK
DB 256 DUP (?)
SSEG ENDS
;Сегмент данных
DSEG SEGMENT
SMP DB ‘Q’ ;Символ
TBL DB ‘0123456789ABCDEF’ ;Таблица 16-ричных цифр
DSEG ENDS
;Сегмент кода
CSEG SEGMENT
ASSUME CS:CSEG, DS:DSEG, SS:SSEG
START:
MOV AX,DSEG ;Инициализация DS
MOV DS,AX
MOV AH,2 ;В AH номер функции вывода
MOV BX,0
;Вывод на экран цифры соответствующей левой тетраде
MOV BL,Smp ;В BL символ
MOV CL,4 ;В CL величина сдвига
SHR BL,CL ;Сдвиг левой тетрады на место правой
MOV DL,Tbl[BX] ;Загрузка цифры из таблицы в DL
INT 21H ;Вывод на экран
;Вывод на экран цифры соответствующей правой тетраде
MOV BL,Smp ;В BL символ
AND BL,00001111B ;Обнуление левой тетрады
MOV DL,Tbl[BX] ;Загрузка цифры из таблицы в DL
INT 21H ;Вывод на экран
;Вывод на экран символа «h»
MOV DL,’h’
INT 21H
CSEG ENDS
END START
Приведенная программы выводит на экран шестнадцатеричный кода буквы «Q», то есть выводятся две шестнадцатеричные цифры, соответствующие тетрадам байта, представляющего код символа «Q». Содержимое каждой тетрады как четырехбитового поля используется в качестве значения индекса для таблицы байтов, каждый из которых представляет шестнадцатеричный символ. Такой прием является распространенным способом перевода внутреннего битового представления во внешнее символьное.
8. Команды сравнения и передачи управления
Команда сравнения CMP сравнивает два числа, вычитая второе из первого, также как и команда SUB. Отличие команд CMP и SUB состоит в том, что инструкция CMP не сохраняет результат, а лишь устанавливает в соответствии с результатом флаги состояния. Основное назначение команды CMP – это организация ветвлений (условных переходов) в ассемблерных программах
Безусловный переход – это переход, который передает управление без сохранения информации возврата всякий раз, когда выполняется. Ему соответствует команда JMP. Эта команда может осуществлять переход вплоть до 32768 байт. Если заранее известно, что переход вперед делается на место, лежащее в диапазоне 128 байт от текущего места, можно использовать команду JMP SHORT LABEL. Атрибут SHORT заставляет Ассемблер сформировать короткую форму команды перехода, даже если он еще не встретил метку LABEL.
Условный переход проверяет текущее состояние машины (флагов или регистров), чтобы определить, передать управление или нет. Команды переходов по условию делятся на две группы:
проверяющие результаты предыдущей арифметической или логической операции Jcc;
управляющие итерациями фрагмента программы (организация циклов) LOOPcc.
Все условные переходы имеют однобайтовое смещение, то есть метка, на которую происходит переход должна находится в том же кодовом сегменте и на расстоянии, не превышающем –128 +127 байт от первого байта следующей команды. Если условный переход осуществляется на место, находящееся дальше 128 байт, то вместо недопустимой команды
JZ ZERO
необходимо использовать специальные конструкции типа:
JNZ CONTINUE
JMP ZERO
CONTINUE:
Первая группа команд Jcc (кроме JCXZ/JECXZ) проверяет текущее состояние регистра флагов (не изменяя его) и в случае соблюдения условия осуществляет переход на смещение, указанное в качестве операнда. Флаги, проверяемые командой, кодируются в ее мнемонике, например: JC – переход, если установлен CF. Сокращения «L» (less – меньше) и «G» (greater – больше) применяются для целых со знаком, а «A» (above – над) и «B» (below – под) для целых без знака. Ниже в таблице показаны команды условного перехода и проверяемые ими флаги.
Таблица 8.1. Команды условного перехода.
Мнемоника
|
Флаги
|
Комментарии
|
OF
|
CF
|
ZF
|
PF
|
SF
|
Проверка флагов
|
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
|
|
Арифметика со знаком
|
JL/JNGE
|
a
|
X
|
X
|
X
|
b
|
a не равно b (SF<>OF)
|
JLE/JNG
|
a
|
X
|
1
|
X
|
b
|
Z или a не равно b
|
JNL/JGE
|
a
|
X
|
X
|
X
|
b
|
a равно b
|
JNLE/JG
|
a
|
X
|
0
|
X
|
b
|
не Z и (a равно b)
|
Арифметика без знака
|
JB/JNAE/JS
|
X
|
1
|
X
|
X
|
X
|
|
JBE/JNA
|
X
|
1
|
1
|
X
|
X
|
CF или Z
|
JNB/JGE
|
X
|
0
|
X
|
X
|
X
|
|
JNBE/JG
|
X
|
0
|
0
|
X
|
X
|
не CF или не Z
|
Буква Х в любой позиции означает, что команда не проверяет флаг. Цифра 0 означает, что флаг должен быть сброшен, а цифра 1 означает, что флаг должен быть установлен, чтобы условие было выполнено (переход произошел).
Команды условного перехода можно разделить на три подгруппы:
Непосредственно проверяющие один из флагов на равенство 0 или 1.
Арифметические сравнения со знаком. Существуют 4 условия, которые могут быть проверены: меньше (JL), меньше или равно (JLE), больше (JG), больше или равно (JGE). Эти команды проверяют одновременно три флага: знака, переполнения и нуля.
Арифметические без знака. Здесь также существует 4 возможных соотношения между операндами. Учитываются только два флага. Флаг переноса показывает какое из двух чисел больше. Флаг нуля определяет равенство.
Ниже приведен фрагмент программы, иллюстрирующий использование команд сравнения и перехода.
CSEG SEGMENT
ASSUME CS:CSEG, DS:DSEG, SS:SSEG
START:
...
MOV BH,X ;Загрузка в BH значения Х
MOV BL,Y ;Загрузка в BL значения Y
CMP BH,BL ;Сравнение BH и BL
JE MET1 ;Если BH=BL, то переход на MET1
JMP MET2 ;Иначе переход на MET2
MET1:
...
JMP MET3
MET2:
...
MET3:
MOV AH,4Ch
INT 21H
CSEG ENDS
END START
JCXZ отличается от других команд условного перехода тем, что она проверяет содержимое регистра CX, а не флагов. Эту команду лучше всего применять в начале условного цикла, чтобы предотвратить вхождение в цикл, если CX=0.
Вторая группа команд условного перехода LOOPcc служит для организации циклов в программах. Все команды цикла используют регистр CX в качестве счетчика цикла. Простейшая из них – команда LOOP. Она уменьшает содержимое CX на 1 и передает управление на указанную метку, если содержимое CX не равно 0. Если вычитание 1 из CX привело к нулевому результату, выполняется команда, следующая за LOOP.
Команда LOOPNE (цикл пока не равно) осуществляет выход из цикла, если установлен флаг нуля или если регистр CX достиг нуля. Команда LOOPE (цикл пока равно) выполняет обратную к описанной выше проверку флага нуля: в этом случае цикл завершается, если регистр CX достиг нуля или если не установлен флаг нуля.
Приведенный ниже фрагмент программы иллюстрирует использование команд организации циклов.
DSEG SEGMENT
BUF DB “0123406789”
DSEG ENDS
CSEG SEGMENT
ASSUME CS:CSEG,DS:DSEG,SS:SSEG
START:
...
MOV BX,OFFSET BUF;В BX – начало буферов
MOV CX,10 ;В CX – длинна буфера
MOV SI,0
M1: MOV DL,[BX+SI] ;В DL – символ из буфера
MOV AH,2 ;в AH номер функции-вывода
INT 21H ;Вывод на экран
INC SI ;Увеличение индекса на 1
LOOP M1 ;Оператор первого цикла
...
CSEG ENDS
END START
9. Подпрограммы и прерывания.
Все современные программы разрабатываются по модульному принципу – программа обычно состоит из одной или нескольких небольших частей, называемых подпрограммами или процедурами, и одной главной программы, которая вызывает эти процедуры на выполнение, передавая им управление процессором. После завершения работы процедуры возвращают управление главной программе и выполнение продолжается с команды, следующей за командой вызова подпрограммы.
Достоинством такого метода является возможность разработки программ значительно большего объема небольшими функционально законченными частями. Кроме того, эти подпрограммы можно использовать в других программах, не прибегая к переписыванию частей программного кода. В довершение ко всему, так как размер сегмента не может превышать 64К, то при разработке программ с объемом кода более 64К, просто не обойтись без модульного принципа.
Язык программирования Ассемблера поддерживает применение процедур двух типов – ближнего (near) и дальнего (far).
Процедуры ближнего типа должны находится в том же сегменте, что и вызывающая программа. Дальний тип процедуры означает, что к ней можно обращаться из любого другого кодового сегмента.
При вызове процедуры в стеке сохраняется адрес возврата в вызывающую программу:
при вызове ближней процедуры – слово, содержащее смещение точки вызова относительно текущего кодового сегмента;
при вызове дальней процедуры – слово, содержащее адрес сегмента, в котором расположена точка возврата, и слово, содержащее смещение точки возврата в этом сегменте.
В общем случае группу команд, образующих подпрограмму, можно никак не выделять в тексте программы. Для удобства восприятия в языке Ассемблера процедуры принято оформлять специальным образом. Описание процедуры имеет следующий синтаксис:
<�имя_процедуры> PROC <�параметр>
<�тело_процедуры>
<�имя_процедуры> ENDP
Следует обратить внимание, что в директиве PROC после имени не ставится двоеточие, хотя имя и считается меткой.
Параметр, указываемый после ключевого слова PROC, определяет тип процедуры: ближний (NEAR) или дальний (FAR). Если параметр отсутствует, то по умолчанию процедура считается ближней.
В общем случае, размещать подпрограмму в теле программы можно где угодно, но при этом следует помнить, что сама по себе подпрограмма выполняться не должна, а должна выполняться лишь при обращении к ней. Поэтому подпрограммы принято размещать либо в конце сегмента кода, после команд завершения программы, либо в самом начале сегмента кода, перед точкой входа в программу. В больших программах подпрограммы нередко размещают в отдельном кодовом сегменте.
|