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

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

Инструкция 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

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

  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)

Пример применения:

.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
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850