BAULA1 (1110624), страница 2
Текст из файла (страница 2)
Ниже в Таблице 7.1 приведены мнемоники команд условного перехода. Некоторые команды имеют разную мнемонику, но выполняются одинаково (переводятся программой Ассемблера в одну и ту же машинную команду), такие команды указаны в одной строке таблицы.
Таблица 7.1. Мнемоника команд условного перехода | ||
КОП | Условие перехода | |
Логическое условие перехода | Результат (rez) команды вычитания или cоотношение операндов op1 и op2 команды сравнения | |
je jz | ZF = 1 | Rez = 0 или op1 = op2 (результат = 0, операнды равны) |
jne jnz | ZF = 0 | rez <> 0 или op1 <> op2 Результат <> 0, операнды не равны |
jg jnle | (SF=OF) and (ZF=0) | rez > 0 или op1 > op2 Знаковый результат > 0, op1 больше op2 |
jge jnl | SF = OF | rez >= 0 или op1 >= op2 Знаковый результат >= 0, т.е. op1 больше или равен (не меньше) op2 |
jl jnge | SF <> OF | rez < 0 или op1 < op2 Знаковый результат < 0, т.е. op1 меньше (не больше или равен) op2 |
jle jng | (SF<>OF) or (ZF=1) | rez <= 0 или op1 <= op2 Знаковый результат <= 0, т.е. op1 меньше или равен(не больше) op2 |
ja jnbe | (CF=0) and (ZF=0) | rez > 0 или op1 > op2 Беззнаковый результат > 0, т.е. op1 выше (не ниже или равен) op2 |
jae jnb jnc | CF = 0 | rez >= 0 или op1 >= op2 Беззнаковый результат >= 0, т.е. op1 выше или равен (не ниже) op2 |
jb jnae jc | CF = 1 | rez < 0 или op1 < op2 Беззнаковый результат < 0, т.е. op1 ниже (не выше или равен) op2 |
jbe jna | (CF=1) or (ZF=1) | rez >= 0 или op1 >= op2 Беззнаковый результат >= 0, т.е. op1 ниже или равен (не выше) op2 |
js | SF = 1 | Знаковый бит разультата (7-й или 15-ый, в зависимости от размера) равен единице |
jns | SF = 0 | Знаковый бит разультата (7-й или 15-ый, в зависимости от размера) равен нулю |
jo | OF = 1 | Флаг переполнения равен единице |
jno | OF = 0 | Флаг переполнения равен нулю |
jp jpe | PF = 1 | Флаг чётности 1 равен единице |
jnp jpo | PF = 0 | Флаг чётности равен единице |
jcxz | CX = 0 | Значение регистра CX равно нулю |
В качестве примера рассмотрим, почему условному переходу jl/jnge соответствует логическое условие перехода SF<>OF. При выполнении команды сравнения cmp op1,op2 или команды вычитания sub op1,op2 нас будет интересовать трактовка операндов как знаковых целых чисел, поэтому возможны два случая, когда первый операнд меньше второго. Во-первых, если при выполнении операции вычитания op1-op2 результат получился правильным, т.е. не было переполнения (OF=0), то бит знака у правильного результата равен единице (SF=1). Во-вторых, при вычитании мог получиться неправильный результат, т.е. было переполнение (OF=0), но в этом случае знаковый бит результата будет неправильным, т.е. равным нулю. Видно, что в обоих случаях эти два флага не равны друг другу, т.е. должно выполняться условие SF<>OF, что и указано в нашей таблице. Для тренировки разберите правила формирования и других условий переходов.
Как видим, команд условного перехода достаточно много, поэтому понятно, почему для них реализован только один формат – близкий короткий относительный переход. Реализация других форматов команд условного перехода привела бы к резкому увеличению числа команд в языке машины и, как следствие, к усложнению центрального процессора и росту объёма программ. В то же время наличие только одного формата команд условного перехода может приводить к плохому стилю программирования. Пусть, например, надо реализовать на Ассемблере условный оператор языка Паскаль
if X>Y then goto L;
Соответствующий фрагмент на языке Ассемблера, реализующий этот оператор для знаковых X,Y
mov ax,X
cmp ax,Y
jg L
. . .
L:
может быть неверным, если расстояние между меткой и командой условного перехода велико (не помещается в байт). В таком случае придётся использовать такой фрагмент на Ассемблере со вспомогательной меткой L1 и вспомогательной командой безусловного перехода:
mov ax,X
cmp ax,Y
jle L1
jmp L
L1:
. . .
L:
Таким образом, на самом деле мы вынуждены реализовывать такой фрагмент программы на языке Паскаль:
if X<=Y then goto L1; goto L; L1:;
Это, конечно, по необходимости, прививает плохой стиль программирования.
В качестве примера использования команд условного перехода рассмотрим программу, которая вводит знаковое число A в формате слова и вычисляет значение X по формуле
include io.asm
; файл с макроопределениями для макрокоманд ввода-вывода
data segment
A dw ?
X dw ?
Diagn db ′Ошибка – большое значение!$′
Data ends
Stack segment stack
db 128 dup (?)
stack ends
code segment
assume cs:code, ds:data, ss:stack
start:mov ax,data; это команда формата r16,i16
mov ds,ax ; загрузка сегментного регистра DS
inint A ; ввод целого числа
mov ax,A ; ax := A
mov bx,ax ; bx := A
inc ax ; ax := A+1
jo Error
cmp bx,2 ; Сравнение A и 2
jle L1 ; Вниз по первой ветви вычисления X
dec bx ; bx := A-1
jo Error
imul bx ; (dx,ax):=(A+1)*(A-1)
jo Error ; Произведение (A+1)*(A-1) не помещается в ax
L: mov X,ax ; Результат берётся только из ax
outint X; Вывод результата
newline
finish
L1: jl L2; Вниз по второй ветви вычисления X
mov ax,4
jmp L; На вывод результата
L2: mov bx,7; Третья ветвь вычисления X
cwd ; (dx,ax):= длинное (A+1) – иначе нельзя!
idiv bx; dx:=(A+1) mod 7, ax:=(A+1) div 7
mov ax,dx
jmp L; На вывод результата
Error:mov dx,offset Diagn
outstr
newline
finish
code ends
end start
В нашей программе мы сначала закодировали вычисление по первой ветви нашего алгоритма, затем по второй и, наконец, по третьей. Программист, однако, может выбрать и другую последовательность кодирования ветвей, это не влияет на суть дела. В нашей программе предусмотрена выдача аварийной диагностики, если результаты операций сложения (A+1), вычитания (A-1) или произведения (A+1)*(A-1) слишком велики и не помещается в одно слово.
Для увеличения и уменьшения операнда на единицу мы использовали команды
inc op1 и dec op1
Здесь op1 может иметь формат r8, r16, m8 и m16. Например, команда inc ax эквивалентна команде add ax,1 , но не меняет флага CF. Таким образом, после этих команд нельзя проверить флаг переполнения, чтобы определить, правильно ли выполнились такие операции над беззнаковыми числами.
Обратите также внимание, что мы использовали команду длинного деления, попытка использовать здесь короткое деление, например
L2: mov bh,7; Третья ветвь вычисления X
idiv bh; ah:=(A+1) mod 7, al:=(A+1) div 7
может привести к ошибке. Здесь остаток от деления (A+1) на число 7 всегда поместится в регистр ah, однако частное (A+1) div 7 может не поместиться в регистр al (пусть A=27999, тогда (A+1) div 7 = 4000 – не поместится в регистр al).
При использовании команд условного перехода мы предполагали, что расстояние от точки перехода да нужной метки небольшое (формата i8), если это не так, то программа Ассемблера выдаст нам соответствующую диагностику об ошибке и нам придётся использовать "плохой стиль программирования", как объяснялось выше. В нашей программе это может случиться только тогда, когда суммарный размер кода, подставляемого вместо макрокоманд outint и finish, будет больше 128 байт (обязательно понять это!).
7.6.3. Команды цикла
Для организации циклов на Ассемблере вполне можно использовать команды условного перехода. Например, цикл языка Паскаль с предусловием while X<0 do S; можно реализовать в виде следующего фрагмента на Ассемблере
L: cmp X,0; Сравнить X с нулём
jge L1
; Здесь будет оператор S
jmp L
L1: . . .
Оператор цикла с постусловием repeat S1; S2;. . .Sk until X<0; можно реализовать в виде фрагмента на Ассемблере
L: ; S1
; S2
. . .
; Sk
cmp X,0; Сравнить X с нулём
jge L
. . .
В этих примерах мы считаем, что тело цикла по длине не превышает примерно 120 байт (это 30-40 машинных команд). Как видим, цикл с постусловием требует для своей реализации на одну команду меньше, чем цикл с предусловием.
Как мы знаем, если число повторений выполнения тела цикла известно до начала исполнения этого цикла, то в языке Паскаль наиболее естественно было использовать цикл с параметром. Для организации цикла с параметром в Ассемблере можно использовать специальные команды цикла. Команды цикла, по сути, тоже являются командами условного перехода и, как следствие, реализуют только близкий короткий относительный переход. Команда цикла
loop L; Метка L заменится на операнд i8
использует неявный операнд – регистр CX и её выполнение может быть так описано с использованием Паскаля:
Dec(CX); {Это часть команды loop, поэтому флаги не меняются!}
if CX<>0 then goto L;
Как видим, регистр CX (который так и называется регистром счётчиком цикла – loop counter), используется этой командой именно как параметр цикла. Лучше всего эта команда цикла подходит для реализации цикла с параметром языка Паскаль вида
for CX:=N downto 1 do S;
Этот оператор можно эффективно реализовать таким фрагментом на Ассемблере:
mov CX,N
jcxz L1
L: . . .; Тело цикла –
. . .; оператор S
loop L
L1: . . .
Обратите внимание, так как цикл с параметром языка Паскаль по существу является циклом с предусловием, то до начала его выполнение проверяется исчерпание значений для параметра цикла с помощью команды условного перехода jcxz L1 , которая именно для этого и была введена в язык машины. Ещё раз напоминаем, что команды циклов не меняют флагов.
Описанная выше команда цикла выполняет тело цикла ровно N раз, где N – беззнаковое число, занесённое в регистр-счётчик цикла CX перед началом цикла. К сожалению, никакой другой регистр нельзя использовать для этой цели (т.к. это неявный параметр команды цикла). Кроме того, в приведённом выше примере реализации цикла тело этого не может быть слишком большим, иначе команда loop L не сможет передать управление на метку L.
В качестве примера использования команды цикла решим следующую задачу. Требуется ввести беззнаковое число N<=500, затем ввести N знаковых целых чисел и вывести сумму тех из них, которые принадлежат диапазону –2000..5000. Можно предложить следующее решение этой задачи.
include io.asm