Инструкция CMP (от слова compare - сравнить) позволяет сравнить значения и установить флаги. Благодаря чему мы можем использовать результат сравнения для выполнения условного перехода. Она принимает два операнда:
cmp left_operand, right_operand
В качестве операндов могут участвовать регистры, переменные, константы и непосредственные операнды. Следует учитывать, оба операнда не могут быть одновременно переменными, и что константы и непосредственные операнды могут быть размером не более 32 бита, даже если первый операнд представляет 64-битный регистр. При этом оба операнда должны представлять целые числа, числа с плавающей точкой НЕ сравниваются.
Как и инструкция SUB она также вычитает второй операнд из первого операнда
и устанавливает флаги кода условия на основе результата вычитания. Отличие состоит в том, что инструкция cmp
не сохраняет результат вычитания в первый операнд, поскольку цель
инструкции cmp состоит лишь в том, чтобы установить флаги кода условия на основе результата вычитания. То есть фактически посредством инструкции cmp мы сравнивает
первый операнд со вторым. Например, возьмем следующую инструкцию
cmp ax, bx
Как здесь будут установлены флаги:
Флаг нуля ZF устанавливается, если AX = BX
Флаг знака SF устанавливается, если результат отрицательный. При этом установка флага НЕ означает, что AX обязательно меньше BX. Если AX = 7FFFh (32767) и BX = 0FFFFh (-1), то вычитание AX - BX дает отрицательное значение 8000h (поэтому будет установлен флаг знака). Другой пример: есть беззнаковые операнды AX = 0FFFFh и BX = 1. Здесь AX больше, чем BX, но их разница составляет 0FFFEh, что по-прежнему отрицательно. Поэтому для сравнения двух значений со знаком надо использовать вместе два флага - флаг знака и флаг переполнения:
Если ((SF = 0) и (OF = 1)) или ((SF = 1) и (OF = 0)), тогда AX меньше, чем BX
Если ((SF = 0) и (OF = 0)) или ((SF = 1) и (OF = 1)), тогда AX больше или равно BX
Таким образом, если флаги SF и OF не равны, то AX меньше, чем BX. Если эти флаги равны, тогда AX больше или равно BX
Флаг переполнения OF устанавливается, если при вычитании произойдет переполнение знакового бита.
Флаг переноса CF устанавливается, если при вычитании AX - BX
потребуется заимствование
Рассмотрим несколько ситуаций:
Сравним 0FFFFh (–1) и 0FFFEh (–2). Инструкция вычисляет выражение (–1) – (–2)
, что равно (+1).
Результат положительный и переполнения не произошло, поэтому оба флага SF и OF равны 0. Соответственно левый операнд (-1) больше или равен правому (-2)
Сравним 8000h (–32 768) и 0001h (1). Инструкция вычисляет выражение –32768 – 1
, что равно -32769.
Поскольку 16-разрядное целое число со знаком не может представлять это значение, значение переносится на 7FFFh (+32 767) и устанавливает флаг переполнения OF.
Результат положительный, поэтому флаг знака SF сбрасывается. SF и OF не равны, левый операнд (–32768) меньше чем правый (1).
Сравним 7FFFh (32767) и 0FFFFh (–1). Инструкция вычисляет выражение 32767 – (–1)
, что равно -32768 или 8000h. Поскольку произошло переполнение
знака, то устанавливает флаг переполнения OF. И поскольку результат отрицательный, устанавливается флаг знака SF. Оба флага SF и OF равны 1.
Соответственно левый операнд (32767) больше или равен правому (-1)
Сравним 0FFFEh (–2) и 0FFFFh (–1). Инструкция вычисляет выражение (–2) – (–1)
, что равно (-1). Переполнения не произошло, поэтому флаг OF равен 0.
И так как результат отрицательный, SF равен 1. Соответственно левый операнд (-2) меньше, чем правый (-1)
Пример применения:
.code main proc mov eax, 0 mov ebx, 1 cmp eax, ebx ; сраниваем значения регистра EAX и регистра EBX jc carry_set mov eax, 0 ret carry_set: mov eax, 1 ret main endp end
Здесь инструкция cmp
сравнивает значение из регистра EAX со значением регистра EBX (фактически вычитает из EAX значение EBX). Поскольку по сути выполняется вычитание 0 -1,
то происходит вычитание с заимствованием, что приведет к установке флага переноса. Соответственно последующая инструкция jc выполнит переход к метке carry_set.
Для упрощения работы с переходами в ассемблере есть ряд инструкций, которые позволяют более точно определить результат сравнения и выполнить переход на определенную метку:
je: проверяет условие ZF == 1
и выполняет переход, если оба операнда равны. Фактически эквивалентна инструкции jz
jne: проверяет условие ZF == 0
и выполняет переход, если оба операнда НЕ равны. Фактически эквивалентна инструкции jnz
ja / jnbe: проверяет одновременно два условия СF == 0
и ZF == 0
(оба условия должны быть истинными). Выполняет переход, если первый операнд больше второго.
Оба операнда беззнаковые.
jae / jnb: проверяет условие СF == 0
и выполняет переход, если первый операнд больше или равен второму.
Оба операнда беззнаковые. Аналогичен инструкции jnc
jb / jnae: проверяет условие СF == 1
и выполняет переход, если первый операнд меньше второго.
Оба операнда беззнаковые. Аналогичен инструкции jc
.
jbe / jna: проверяет одновременно два условия СF == 1
и ZF == 1
(достаточно, чтобы выполнялось хотя бы одно из этих условий).
Выполняет переход, если первый операнд меньше или равен второму. Оба операнда беззнаковые.
jg / jnle: проверяет одновременно два условия SF == OF
и ZF == 0
(оба условия должны быть истинными). Выполняет переход, если первый операнд больше второго.
Оба операнда со знаком.
jge / jnl: проверяет условие SF == OF
и выполняет переход, если первый операнд больше или равен второму.
Оба операнда со знаком.
jl / jnge: проверяет условие SF != OF
(флаги SF и OF не должны быть равны) и выполняет переход, если первый операнд меньше второго.
Оба операнда со знаком.
jle / jng: проверяет одновременно два условия SF != OF
и ZF == 1
(достаточно, чтобы выполнялось хотя бы одно из этих условий).
Выполняет переход, если первый операнд меньше или равен второму. Оба операнда со знаком.
В этом списке стоит отметить два момента. Во-первых, инструкции сравнения для беззнаковых чисел и чисел со знаком различаются. Во-вторых, ряд инструкций имеют синонимичные конструкции,
которые выполняют одно и то же действие и на бинарном уровне имеют один и тот же код, например, вместо инструкции jb
можно использовать jc
или
jnae
.
Пример применения инструкций:
.data n1 byte 22 n2 byte 33 .code main proc mov al, n1 cmp al, n2 ; сраниваем значения регистра AL и переменной n2 jne not_equal ; если значения не равны, переходим к метке not_equal mov eax, 0 ret not_equal: mov eax, 1 ret main endp end
В данном случае помещаем в регистр AL значение переменной n1 и затем сравниваем его в переменной n2
mov al, n1 cmp al, n2
Оба значения представляют тип byte, то есть являются 8-разрядными беззнаковыми числами. С помощью инструкции jne проверяем флаг нуля, и если он НЕ установлен (то есть числа НЕ равны), переходим к метке not_equal:
jne not_equal