Сравнение. Инструкция CMP

Последнее обновление: 12.10.2023

Инструкция cmp (от слова compare - сравнить) позволяет сравнить значения и установить флаги. Благодаря чему мы можем использовать результат сравнения для выполнения условного перехода. Инструкция принимает два операнда:

cmp left_operand, right_operand

В качестве операндов могут участвовать регистры, переменные, непосредственные операнды. Если сравнивается непосредственный операнд, то он указывается вторым. При этом оба операнда должны представлять целые числа, числа с плавающей точкой НЕ сравниваются.

Как и инструкция SUB она также вычитает второй операнд из первого операнда и устанавливает флаги кода условия на основе результата вычитания. Отличие состоит в том, что инструкция cmp не сохраняет результат вычитания, поскольку цель инструкции cmp состоит лишь в том, чтобы установить флаги кода условия на основе результата вычитания. То есть фактически посредством инструкции cmp мы сравниваем первый операнд со вторым. Например, возьмем следующую инструкцию

cmp rax, rbx

Как здесь будут установлены флаги:

  • Флаг нуля ZF устанавливается, если RAX = RBX

  • Флаг переноса CF устанавливается, если при вычитании AX - BX потребуется заимствование

  • Флаг переполнения OF устанавливается, если при вычитании произойдет переполнение знакового бита.

  • Флаг знака SF устанавливается, если результат отрицательный. При этом установка флага НЕ означает, что RAX обязательно меньше RBX. Например, если AX = 32767 (7FFFh) и BX = –1 (0FFFFh), то вычитание AX - BX дает отрицательное значение 8000h (поэтому будет установлен флаг знака). Другой пример: есть беззнаковые операнды AX = 0FFFFh и BX = 1. Здесь AX больше, чем BX, но их разница составляет 0FFFEh, что по-прежнему отрицательно. Поэтому для сравнения двух значений со знаком надо использовать вместе два флага - флаг знака и флаг переполнения:

    • Если ((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

Рассмотрим несколько ситуаций:

  1. Сравним 0xFFFF (–1) и 0xFFFE (–2). Инструкция вычисляет выражение (–1) – (–2), что равно (+1). Результат положительный и переполнения не произошло, поэтому оба флага SF и OF равны 0. Соответственно левый операнд (-1) больше или равен правому (-2)

  2. Сравним 0x8000 (–32 768) и 0x0001 (1). Инструкция вычисляет выражение –32768 – 1, что равно -32769. Поскольку 16-разрядное целое число со знаком не может представлять это значение, значение переносится на 0x7FFF (+32 767) и устанавливает флаг переполнения OF. Результат положительный, поэтому флаг знака SF сбрасывается. SF и OF не равны, левый операнд (–32768) меньше чем правый (1).

  3. Сравним 0x7FFF (32767) и 0xFFFF (–1). Инструкция вычисляет выражение 32767 – (–1), что равно -32768 или 0x8000. Поскольку произошло переполнение знака, то устанавливает флаг переполнения OF. И поскольку результат отрицательный, устанавливается флаг знака SF. Оба флага SF и OF равны 1. Соответственно левый операнд (32767) больше или равен правому (-1)

  4. Сравним 0xFFFE (–2) и 0xFFFF (–1). Инструкция вычисляет выражение (–2) – (–1), что равно (-1). Переполнения не произошло, поэтому флаг OF равен 0. И так как результат отрицательный, SF равен 1. Соответственно левый операнд (-2) меньше, чем правый (-1)

Возьмем следующую программу на Linux:

global _start

section .text
_start:
    mov rax, 0
    mov rbx, 1
    cmp rax, rbx    ; сраниваем значения регистра RAX и регистра RBX
    jc carry_set    ; если произошло заимствование
    mov rdi, 11
    jmp exit
carry_set:
    mov rdi, 22
exit:    
    mov rax, 60
    syscall 

Здесь инструкция cmp сравнивает значение из регистра RAX со значением регистра RBX (фактически вычитает из RAX значение RBX). Поскольку по сути выполняется вычитание 0 (RAX) - 1 (RBX), то происходит вычитание с заимствованием, что приведет к установке флага переноса. Соответственно последующая инструкция jc выполнит переход к метке carry_set.

Аналогичный пример на Windows:

global _start

section .text
_start:
    mov rax, 0
    mov rbx, 1
    cmp rax, rbx    ; сраниваем значения регистра RAX и регистра RBX
    jc carry_set    ; если произошло заимствование
    mov rdi, 11
    jmp exit
carry_set:
    mov rdi, 22
exit:
    mov rax, rdi     
    ret    

Инструкции перехода

Для упрощения работы с переходами в ассемблере есть ряд инструкций, которые позволяют более точно определить результат сравнения и выполнить переход на определенную метку:

  • 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.

Пример применения инструкций на Linux:

global _start

section .text
_start:
    mov rcx, 33
    cmp rcx, 33     ; сравниваем значение из RCX с числом 33. Фактически вычитаем RCX - 33
    je equal        ; если значения равны, переходим к метке equal
    mov rdi, 2      ; если значения НЕ равны
    jmp exit
equal:
    mov rdi, 4      ; если значения равны
exit:
    mov rax, 60
    syscall 

В данном случае помещаем в регистр RCX некоторое число и сравниваем его с числом 33

mov rcx, 33
cmp rcx, 33

С помощью инструкции je проверяем флаг нуля и, если он установлен (то есть числа равны), переходим к метке equal:

je equal

Аналогичный пример для Windows:

global _start

section .text
_start:
    mov rcx, 33
    cmp rcx, 33     ; сравниваем значение из RCX с числом 33. Фактически вычитаем RCX - 33
    je equal        ; если значения равны, переходим к метке equal
    mov rdi, 2      ; если значения НЕ равны
    jmp exit
equal:
    mov rdi, 4      ; если значения равны
exit:
    mov rax, rdi     
    ret    

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850