Ассемблер
Как работает CMP в ассемблере
CMP в ассемблере работает как вычитание без сохранения результата. Инструкция сравнивает два операнда, мысленно выполняет левый операнд - правый операнд, выставляет флаги процессора и оставляет сами значения без изменений. После cmp обычно сразу идет условный переход: je, jne, jl, jg, jb, ja и другие
Простой пример:
mov eax, 10
cmp eax, 10
je equal
Здесь cmp eax, 10 проверяет, что получилось бы при eax - 10. Так как результат был бы нулевым, выставляется флаг ZF, и переход je equal срабатывает
Что именно делает CMP
Если написать:
cmp eax, ebx
процессор сравнит eax и ebx так, будто выполняет:
eax - ebx
Но результат не запишется обратно в eax. Меняются только флаги. Поэтому после команды:
mov eax, 7
mov ebx, 3
cmp eax, ebx
в eax все еще будет 7, а в ebx все еще будет 3
Какие флаги важны
Для первого понимания достаточно четырех флагов:
ZF— zero flag, выставляется, если значения равны;SF— sign flag, показывает знак результата сравнения;CF— carry flag, важен для беззнаковых сравнений;OF— overflow flag, важен для знаковых сравнений.
Условные переходы смотрят на эти флаги. Например, je смотрит на ZF, а jl оценивает сочетание SF и OF
Равенство и неравенство
cmp eax, 5
je is_equal
jne is_not_equal
Если eax равен 5, сработает je. Если не равен, сработает jne
Для проверки равенства не нужно думать о знаковых и беззнаковых числах. Равенство битов одинаково в обоих случаях
Больше и меньше: signed и unsigned
Вот здесь начинается важное различие
Для знаковых чисел:
cmp eax, ebx
jl less_signed
jg greater_signed
Для беззнаковых чисел:
cmp eax, ebx
jb below_unsigned
ja above_unsigned
jl означает «меньше как signed». jb означает «ниже как unsigned». Одни и те же биты могут означать разные числа. Например, байт 0FFh как signed может означать -1, а как unsigned — 255
Почему нельзя вставлять команды между CMP и переходом
Так правильно:
cmp eax, 10
je equal
Так опасно:
cmp eax, 10
add ebx, 1
je equal
Инструкция add тоже меняет флаги. Поэтому je будет смотреть уже не на результат сравнения eax с 10, а на флаги после add
Если между cmp и переходом стоит команда, которая не меняет флаги, код может быть корректным, но новичку лучше держать переход сразу после сравнения. Так легче читать программу и искать ошибку
CMP и обычное вычитание
sub и cmp похожи тем, что оба используют логику вычитания и выставляют флаги. Отличие в том, что sub меняет левый операнд:
sub eax, 3
а cmp только проверяет:
cmp eax, 3
Если нужно сохранить число, используйте cmp. Если нужно действительно вычесть, используйте sub
Частые ошибки
Думают, что CMP меняет регистр
Не меняет. После cmp eax, 5 значение eax остается прежним
Используют не тот переход
Для signed-сравнений используйте jl, jle, jg, jge. Для unsigned — jb, jbe, ja, jae
Ломают флаги перед переходом
Ставьте условный переход сразу после cmp, пока учитесь
Сравнивают разные размеры
Нельзя просто так сравнить 32-битный регистр с 64-битным операндом. Размеры должны соответствовать синтаксису и платформе
Что почитать дальше по ассемблеру
Если нужен общий маршрут по теме, откройте рубрику ассемблеру. Для соседних задач пригодятся эти разборы:
- CMP в ассемблере: как работает сравнение
- CMP в ассемблере
- CMP, JMP, JE, JNZ: условия и циклы в ассемблере
- JP в ассемблере: как работает переход