Инструкция cmp (от слова compare - сравнить) позволяет сравнить значения и установить флаги. Благодаря чему мы можем использовать результат сравнения для выполнения условного перехода. Для сравнения разных типов предназначена своя разновидность инструкции cmp:
cmpq: для сравнения 64-разрядных чисел
cmpl: для сравнения 32-разрядных чисел
cmpw: для сравнения 16-разрядных чисел
cmpb: для сравнения 8-разрядных чисел
Инструкция принимает два операнда:
cmpq left_operand, right_operand
В качестве операндов могут участвовать регистры, переменные, непосредственные операнды. Если сравнивается непосредственный операнд, то он идет первым. При этом оба операнда должны представлять целые числа, числа с плавающей точкой НЕ сравниваются.
Как и sub
, инструкция cmp также вычитает левый (первый) операнд из правого (второго) операнда
и устанавливает флаги кода условия на основе результата вычитания. Отличие состоит в том, что инструкция cmp
не сохраняет результат вычитания, поскольку цель
инструкции cmp состоит лишь в том, чтобы установить флаги кода условия на основе результата вычитания. То есть фактически посредством инструкции cmp мы сравнивает
первый операнд со вторым. Например, возьмем следующую инструкцию
cmpq %rbx, %rax
Как здесь будут установлены флаги:
Флаг нуля ZF устанавливается, если RAX = RBX
Флаг переполнения OF устанавливается, если при вычитании произойдет переполнение (изменение) знакового бита.
Флаг переноса CF устанавливается, если при вычитании RAX - RBX
потребуется заимствование
Флаг знака SF устанавливается, если результат отрицательный. При этом установка флага НЕ означает, что RAX обязательно меньше RBX. Например, если AX = 32767 (0x7FFF) и BX = –1 (0xFFFF), то вычитание AX из BX дает отрицательное значение 8000h (поэтому будет установлен флаг знака). Другой пример: есть беззнаковые операнды AX = 0xFFFF и BX = 1. Здесь AX больше, чем BX, но их разница составляет 0xFFFE, что по-прежнему отрицательно. Поэтому для сравнения двух значений со знаком надо использовать вместе два флага - флаг знака и флаг переполнения:
Если ((SF = 0) и (OF = 1)) или ((SF = 1) и (OF = 0)), тогда RAX меньше, чем RBX
Если ((SF = 0) и (OF = 0)) или ((SF = 1) и (OF = 1)), тогда RAX больше или равно RBX
Таким образом, если флаги SF и OF не равны, то RAX меньше, чем RBX. Если эти флаги равны, тогда RAX больше или равно RBX
Рассмотрим несколько ситуаций:
Сравним 0xFFFF (–1) и 0xFFFE (–2). Инструкция вычисляет выражение (–1) – (–2)
, что равно (+1).
Результат положительный и переполнения не произошло, поэтому оба флага SF и OF равны 0. Соответственно левый операнд (-1) больше или равен правому (-2)
Сравним 0x8000 (–32 768) и 0x0001 (1). Инструкция вычисляет выражение –32768 – 1
, что равно -32769.
Поскольку 16-разрядное целое число со знаком не может представлять это значение, значение переносится на 0x7FFF (+32 767) и устанавливает флаг переполнения OF.
Результат положительный, поэтому флаг знака SF сбрасывается. SF и OF не равны, левый операнд (–32768) меньше чем правый (1).
Сравним 0x7FFF (32767) и 0xFFFF (–1). Инструкция вычисляет выражение 32767 – (–1)
, что равно -32768 или 0x8000. Поскольку произошло переполнение
знака, то устанавливает флаг переполнения OF. И поскольку результат отрицательный, устанавливается флаг знака SF. Оба флага SF и OF равны 1.
Соответственно левый операнд (32767) больше или равен правому (-1)
Сравним 0xFFFE (–2) и 0xFFFF (–1). Инструкция вычисляет выражение (–2) – (–1)
, что равно (-1). Переполнения не произошло, поэтому флаг OF равен 0.
И так как результат отрицательный, SF равен 1. Соответственно левый операнд (-2) меньше, чем правый (-1)
Пример применения:
.globl _start .text _start: movq $1, %rbx movq $0, %rax cmpq %rbx, %rax # сравниваем RAX и RBX. Фактически вычитаем RAX - RBX jc carry_set # если произошел перенос movq $2, %rdi # если нет переноса jmp exit carry_set: movq $4, %rdi # если есть перенос exit: movq $60, %rax syscall
Здесь инструкция cmpq
сравнивает значение из регистра RAX со значением регистра RBX (фактически вычитает из RAX значение RBX).
Поскольку по сути выполняется вычитание 0 (RAX) - 1 (RBX)
, то происходит вычитание с заимствованием, что приведет к установке флага переноса. Соответственно последующая инструкция
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
.
Пример применения инструкций:
.globl _start .text _start: movq $33, %rcx movq $22, %rdx cmpq %rcx, %rdx # сравниваем RCX и RDX. Фактически вычитаем RDX - RCX je equal # если значения равны, переходим к метке equal movq $2, %rdi # если значения НЕ равны jmp exit equal: movq $4, %rdi # если значения равны exit: movq $60, %rax syscall
В данном случае помещаем в регистры RCX и RDX некоторые числа и сравниваем их
movq $33, %rcx movq $22, %rdx cmpq %rcx, %rdx
С помощью инструкции je проверяем флаг нуля и, если он установлен (то есть числа равны), переходим к метке equal:
je equal