Сравнение и инструкции CMP, CMN и TST

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

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

CMP A, B

Инструкция сравнивает значение A со значением B. В качестве A могут выступать 64-разрядные регистры X0-X30 или 32-разрядные регистры W0-W30 или регистр указателя стека SP/WSP. В качестве B - те же регистры или непосредственный операнд.

В реальности эта инструкция вычитает B из значения A с помощью инструкции SUBSинструкции и обновляет флаги состояния NZCV. Фактически она эквивалентна

SUBS XZR, A, B     // для 64-разрядных операндов
SUBS WZR, A, B      // для 32-разрядных операндов

Выполнив сравнение инструкцией CMP, мы можем проверить флаги с помощью условий:

  • EQ проверяет A == B

  • NE проверяет A != B

  • HI проверяет A > B (если числа беззнаковые)

  • LS проверяет A <= B (если числа беззнаковые)

  • GE проверяет A >= B (если числа со знаком)

  • LT проверяет A < B (если числа со знаком)

  • GT проверяет A > B (если числа со знаком)

  • LE проверяет A <= B (если числа со знаком)

Например, сравним два числа на равенство

.global _start

_start:
    mov x0, #3
    mov x1, #3
    cmp x0, x1     // сравним Х0 и Х1

    b.eq equal      // если они равны - переход к метке equal
    mov x0, #255   // если не равны, Х0=255
    b exit
equal:
    mov x0, #1   // если равны, Х0=1
exit:
    mov x8, #93         // номер функции Linux для выхода из программы - 93
    svc 0               // вызываем функцию и выходим из программы

Здесь мы сравниваем значения регистров Х0 и Х1. Фактически CMP вычитает Х1 из Х0. Если оба значения равны, то результат равен нулю, и соответственно Z-флаг будет установлен. И далее команда B.EQ выполняет переход к метке equal, если Z-флаг установлен.

CMN

Кроме инструкции CMP также можно использовать инструкцию CMN (compare negative). Она имеют аналогичную форму:

CMN A, B

Эта инструкция принимает те же операнды, только вместо вычитания выполняет сложение. То есть фактически эквивалентна

ADDS XZR, A, B      // для 64-разрядных
ADDS WZR, A, B      // для 32-разрядных

Соответственно и логика проверки операндов будет отличаться:

  • EQ проверяет A == -B

  • NE проверяет A != -B

  • HI проверяет A > -B (если числа беззнаковые)

  • LS проверяет A <= -B (если числа беззнаковые)

  • GE проверяет A >= -B (если числа со знаком)

  • LT проверяет A < -B (если числа со знаком)

  • GT проверяет A > -B (если числа со знаком)

  • LE проверяет A <= -B (если числа со знаком)

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

.global _start

_starts:
    mov w1, #-1
    cmn w1, #1     // сравним W0 == -1

    b.eq equal      // если они равны - переход к метке equal
    mov x0, #1   // если не равны, Х0=1
    b exit
equal:
    mov x0, #255   // если равны, Х0=255
exit:
    mov x8, #93         // номер функции Linux для выхода из программы - 93
    svc 0               // вызываем функцию и выходим из программы

Инструкция TST

Поразрядная инструкция TST применяется, чтобы узнать, содержат ли определенные биты 1. Она нередко применяется для проверки условия. Фактически она применяет операцию ANDS (логическое И) для передаваемых ей двух операндов, только в отличие от инструкции ANDS не изменяет значения регистров.

TST Xn, Xm/#imm
TST Wn, Wm/#imm

В качестве первого операнда применяется 64- или 32-разрядный регистр, в качестве второго операнда может выступать регистр или непосредственный операнд. Эта инструкция также является псевдонимом для инструкции ANDS, которой в качестве первого операнда передается нулевой регистр.

ANDS XZR, Xn, Xm/#imm
ANDS WZR, Wn, Wm/#imm

TST устанавливает флаг N, если знаковый бит результата равен 1, и также устанавливает флаг Z, если результат равен 0. Флаги C и V сбрасываются в 0 и в контекста данной инструкции не играют никакой роли.

При сравнении TST A, B мы можем использовать два условия:

  • EQ: биты B в A не найдены или (A & B) == 0

  • NE: биты B в A найдены или (A & B) != 0

Например, определим, установлен ли в регистре бит 2 (0b0100):

.global _start

_start:
    mov x1, #0b1101
    tst x1, #0b0100

    b.ne found      // если установленные биты 0b0100 НАЙДЕНЫ в Х0 - переход к метке found
    mov x0, #1   // если биты не найдены, Х0=1
    b exit
found:
    mov x0, #2   // если биты найдены, Х0=2
exit:
    mov x8, #93         // номер функции Linux для выхода из программы - 93
    svc 0               // вызываем функцию и выходим из программы

В данном случае в регистр Х1 помещаем двоичное число 0b1101. Затем инструкция TST проверяет установку бита номер 2. Поскольку в данном случае такой бит установлен - "0b1101", то результат будет не равен 0, поэтому флаг нуля будет сброшен. Соответственно выполнение перейдет к метке found.

Другой более практичный пример. Как известно, буквы в нижнем регистре отличаются от заглавных букв в таблице ASCII установленным 6-тым битом. Например

Бинарный код симвоа "A": 01000001
Бинарный код симвоа "a": 01100001

Поэтому с помощью инструкции TST можно легко проверить, является символ буквой с верхнем или нижнем регистре (для простоты примера опустим тот факт, что 6 -1 бит может быть установлен не только у символов):

.global _start

_start:
    mov x1, #'b'          // помещаем символ для теста
    tst x1, #0b00100000     // проверка шестого бита

    b.ne low      // если установлен бит 00100000 - переход к метке low
    mov x0, #8   // если буква в верхнем регистре, Х0=8
    b exit
low:
    mov x0, #16   // буква в нижнем регистре, Х0=16
exit:
    mov x8, #93         // номер функции Linux для выхода из программы - 93
    svc 0               // вызываем функцию и выходим из программы

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