Также в ассемблере есть ряд инструкций, которые выполняют условные переходы в зависимости от установки битов в регистре FLAGS. В частности, учитываются четыре бита - carry (флаг переноса), overflow (флаг переполнения), sign (флаг знака) и zero (флаг нуля):
CF (флаг переноса): устанавливается, если происходит беззнаковое переполнение, то есть при сумме с переносом или вычитании с заимствованием (например, при сумме чисел 0FFh и 01h). Если переполнение не происходит, то флаг не устанавливается.
OF (флаг переполнения): установливается, если происходит переполнение со знаком, а именно когда переполняется бит, следующий за старшим
знаковым битом. Например, при сумме чисел 7Fh и 01h. В двоичной системе это будет операция 0111 1111 + 0000 0001
, результатом которой формально
будет число 1000 0000
. Таким образом, произошло изменение старшего знакового бита.
SF (флаг знака): устанавливается, если старший бит результата установлен. В противном случае флаг знака сброшен (то есть флаг знака отражает состояние старшего бита результата).
ZF (флаг нуля): устанавливается, если результат вычисления дает 0. Если результат ненулевой, флаг сброшен
Эти флаги устанавливаются в результате различных операций. В частности, инструкции add, sub, and, or, xor и not влияют на установку флагов. А mov или lea не влияют.
Для проверки этих флагов и выполнения условного перехода в ассемблере есть следующие инструкции:
jc: выполняет переход к метке, если флаг переноса установлен
jnc: выполняет переход к метке, если флаг переноса НЕ установлен
jo: выполняет переход к метке, если флаг переполнения установлен
jno: выполняет переход к метке, если флаг переполнения не установлен
js: выполняет переход к метке, если флаг знака установлен
jns: выполняет переход к метке, если флаг знака не установлен
jz: выполняет переход к метке, если флаг нуля установлен
jnz: выполняет переход к метке, если флаг нуля не установлен
Все эти инструкции принимают один операнд - метку, к которой выполняется переход. Например:
.code main proc mov eax, 0FFFFFFFFh mov ebx, 1 add eax, ebx jc carry_set mov eax, 0 ret carry_set: mov eax, 1 ret main endp end
Здесь в регистр EAX загружаем число 0FFFFFFFFh, а в регистр EBX - число 1. Затем с помощью инструкции add
складываем значения EAX и EBX и результат помещаем в EAX.
mov eax, 0FFFFFFFFh mov ebx, 1 add eax, ebx
Если произошел перенос, то устанавливается флаг переноса. С помощью инструкции:
jc carry_set
Проверям, имеет ли место перенос. И если есть переполнение, и соответственно флаг переноса установлен в 1, то выполняем переход к метке carry_set. Если флаг переноса НЕ установлен, то выполняем последующие инструкции - в регистр EAX помещаем число 0 и выходим из процедуры:
mov eax, 0 ret
Если флаг установлен, то переходим к метке carry_set и в регистр EAX помещаем число 1 и выходим из процедуры:
carry_set: mov eax, 1 ret
В данном случае поскольку при сложении чисел 0FFFFFFFFFh и 1 результат будет равен 100000000h, соответственно будет выполняться перенос, то будет происходить переход к метке overflow, а в регистре EAX окажется число 1.
Подобным образом можно работать с другими инструкциями условного перехода. Например:
.code main proc mov rdx, 5 mov rcx, 5 sub rdx, rcx jz zero_set mov eax, 2 ret zero_set: mov eax, 4 ret main endp end
Здесь помещаем в регистры RDX и RCX некоторые числа и вычитаем из значения регистра RDX число регистра RCX. Если эти числа равны, то результатом будет число ноль, и будет усоановлен флаг нуля ZF. То есть флаг нуля позволяет проверить два числа на равенство при применении вычитания. И в данном случае мы проверяем установку флага нуля, и если он установлен, переходим к метке zero_set.
Используя подобные инструкции, легко создать циклические конструкции:
.code main proc mov rax, 0 mov rcx, 5 main_loop: add rax, 2 sub rcx, 1 jnz main_loop ret main endp end
Здесь регистр выступает в роли счетчика. После метки loop фактически идет цикл. В цикле увеличиваем значение регистра RAX на 2. И затем уменьшаем значение регистра RCX на 1.
Далее с помощью инструкции jnz main_loop
проверяем флаг нуля. Если флаг нуля НЕ установлен (то есть если в RCX НЕ нулевое значение), то переходим обратно к метке main_loop и повторяем действия.
Если вдруг после вычитания в регистре RCX оказался ноль, то выходим из этого цикла и переходим к следующей инструкции - ret
, то есть выходим из процедуры.