Арифметика: add, sub, mul, div
Урок разбирает арифметические команды и их особенности — особенно неочевидное деление.
Целочисленная арифметика в процессоре работает в фиксированной разрядности, поэтому за результатом следят флаги переполнения.
Сложение и вычитание
Самые простые команды. add и sub меняют приёмник и выставляют флаги:
add rax, rbx ; rax = rax + rbx
sub rax, 10 ; rax = rax - 10
inc rax ; rax = rax + 1 (короче, чем add rax,1)
dec rax ; rax = rax - 1Умножение и его коварство
Команда mul rbx умножает rax на rbx, но результат может не влезть в 64 бита, поэтому он кладётся в пару регистров: старшая половина в rdx, младшая в rax. Для знакового умножения есть imul, у которого удобнее формы: imul rax, rbx, 5.
Деление: главная ловушка
Деление div rbx устроено непривычно. Делимое берётся из пары rdx:rax, и результат раскладывается так:
| Регистр | До div | После div rbx |
| rdx:rax | делимое (128 бит) | — |
| rax | — | частное |
| rdx | — | остаток |
Перед делением старшую часть rdx обязательно очищают (для беззнакового — xor rdx, rdx), иначе делимое будет огромным и div «упадёт».
Как работает под капотом
Смоделируем целочисленное деление с частным и остатком на Python — ровно то, что делает div:
dividend = 47
divisor = 5
quotient = dividend // divisor # частное -> rax
remainder = dividend % divisor # остаток -> rdx
print("47 / 5 =", quotient, "остаток", remainder)Вывод:
47 / 5 = 9 остаток 2
Процессор за одну команду div получает и частное, и остаток — именно поэтому в C операции / и % часто компилируются в одно деление.
Частые ошибки
- Забыть очистить
rdxпередdiv. Самая частая причина «деления на ноль»/аварии, хотя делитель не ноль. - Перепутать
mulиimul.mulбеззнаковое,imulзнаковое; для отрицательных нуженimul. - Не помнить, что
mulпортитrdx. Если там были данные, они затрутся старшей половиной произведения.
Итог
add/sub/inc/dec— простая арифметика с обновлением флагов.mul/imulкладут результат в паруrdx:rax.divделитrdx:rax, даёт частное вraxи остаток вrdx.- Перед
divобязательно очищаютrdx(xor rdx, rdx).