Язык ассемблера - Арифметические инструкции

Арифметические инструкции — основа математических операций процессора. В этой главе подробно рассматриваются сложение, вычитание, умножение, деление и связанные с ними инструкции операций с переносом в архитектуре x86.


ADD - Инструкция сложения

ADDДобавляет исходный операнд к операнду назначения и сохраняет результат в операнде назначения.

Пример

; Путь к файлу: add_demo.asm
; Пример инструкции ADD: вычислить 100 + 200 + 300

section .data
    a dd 100
    b dd 200
    c dd 300
    sum dd 0

section .text
    global _start

_start:
    ; Способ 1: регистр += непосредственное значение
    mov eax, [a]                ; eax = 100
    add eax, 200                ; eax = 100 + 200 = 300

    ; Способ 2: регистр += регистр
    mov ebx, [b]                ; ebx = 200
    add eax, ebx                ; eax = 300 + 200 = 500

    ; Способ 3: регистр += память
    add eax, [c]                ; eax = 500 + 300 = 800

    ; Сохранить результат
    mov [sum], eax              ; sum = 800

    ; Влияние сложения на флаги
    mov eax, 0xFFFFFFFF         ; eax = наибольшее 32-битное беззнаковое число
    add eax, 1                  ; eax = 0 (переполнение приводит к циклическому переносу)
    ; CF = 1 (сгенерирован перенос)
    ; ZF = 1 (результат равен 0)
    ; OF = 0 (нет переполнения с точки зрения знаковых чисел)

    mov eax, 1
    mov ebx, 0
    int 0x80

SUB — инструкция вычитания

SUBВычитает исходный операнд из операнда назначения и сохраняет результат в операнде назначения.

Пример

; Путь к файлу: sub_demo.asm
; Пример инструкции SUB

section .data
    x dd 1000
    y dd 300

section .text
    global _start

_start:
    ; Простое вычитание
    mov eax, [x]                ; eax = 1000
    sub eax, [y]                ; eax = 1000 - 300 = 700

    ; Влияние вычитания на флаги
    mov eax, 10
    sub eax, 20                 ; eax = -10 (т. е. 0xFFFFFFF6)
    ; CF = 1 (сгенерирован заём: 10 < 20)
    ; SF = 1 (результат отрицательный)
    ; ZF = 0 (результат ненулевой)
    ; OF = 0 (нет знакового переполнения)

    ; Вычитание самого из себя: часто используется для обнуления
    mov eax, 12345
    sub eax, eax                ; eax = 0
    ; ZF = 1, CF = 0
    ; Это классический способ обнулить регистр (эффективнее, чем mov eax, 0)

    mov eax, 1
    mov ebx, 0
    int 0x80

sub eax, eax— классический приём обнуления регистра; он занимает всего 2 байта, тогда какmov eax, 0занимает 5 байт. Обычно используется в сценариях, требующих экстремальной оптимизации размера (например, в шелл-коде).


INC / DEC — инструкции инкремента/декремента

Инструкции прибавления 1 и вычитания 1 компактнее, чем ADD/SUB:

Пример

; пример INC и DEC

section .data
    counter dd 0

section .text
    global _start

_start:
    mov dword [counter], 0      ; counter = 0
    inc dword [counter]         ; counter = 1 (операнд в памяти)
    inc dword [counter]         ; counter = 2

    mov ecx, 10
    dec ecx                     ; ecx = 9
    dec ecx                     ; ecx = 8

    ; INC/DEC не влияют на флаг CF (это важное отличие от ADD/SUB)
    ; но они влияют на ZF, SF, OF, PF

    ; Использование INC в цикле
    mov ecx, 5
    mov eax, 0
loop_inc:
    inc eax                     ; eax увеличивается на 1 каждый раз
    loop loop_inc               ; Повторить 5 раз, в итоге eax = 5

    mov ebx, eax                ; Возвращаемое значение = 5
    mov eax, 1
    int 0x80

MUL - беззнаковое умножение

MULВыполняет беззнаковое умножение. Правила умножения несколько специфичны:

Размер операндаМножительМножимое (неявное)Место хранения результата
1 байтЛюбой 8-битный регистр или памятьALAX = AL × операнд
2 байтаЛюбой 16-битный регистр или памятьAXDX:AX = AX × операнд
4 байтаЛюбой 32-битный регистр или памятьEAXEDX:EAX = EAX × операнд

Пример

; Путь к файлу: mul_demo.asm
; Пример беззнакового умножения MUL

section .data
    val1 dd 1000
    val2 dd 2000
    result_low dd 0
    result_high dd 0

section .text
    global _start

_start:
    ; 32-битное умножение: EDX:EAX = EAX × операнд
    mov eax, [val1]             ; eax = 1000
    mul dword [val2]            ; edx:eax = 1000 × 2000 = 2,000,000
    ; Результат = 2,000,000 = 0x001E8480
    ; EAX = 0x001E8480 (младшие 32 бита)
    ; EDX = 0x00000000 (старшие 32 бита, потому что результат не превысил 32 бита)

    ; Демонстрация умножения больших чисел (результат превышает 32 бита)
    mov eax, 0xFFFFFFFF         ; eax = 4,294,967,295
    mov ebx, 2                  ; ebx = 2
    mul ebx                     ; edx:eax = 0xFFFFFFFF × 2
    ; EAX = 0xFFFFFFFE
    ; EDX = 0x00000001 (старшие 32 бита)
    ; CF = 1 (результат превышает 32 бита)

    ; Сохранить результат
    mov [result_low], eax
    mov [result_high], edx

    mov eax, 1
    mov ebx, 0
    int 0x80

IMUL — знаковое умножение

IMULИспользуется для умножения чисел со знаком, имеет три формы:

Пример

; Путь к файлу: imul_demo.asm
; Пример знакового умножения IMUL

section .data
    a dd -100
    b dd 3

section .text
    global _start

_start:
    ; Однооперандная форма: то же, что и MUL
    mov eax, [a]                ; eax = -100
    imul dword [b]              ; edx:eax = -100 × 3 = -300
    ; EAX = -300(0xFFFFFED4)
    ; EDX = 0xFFFFFFFF (расширение знака)

    ; Двухоперандная форма: reg = reg × operand
    mov ebx, [a]                ; ebx = -100
    imul ebx, [b]               ; ebx = -100 × 3 = -300
    ; Результат должен помещаться в 32 bits

    ; Трёхоперандная форма: reg = operand1 × immediate
    imul ecx, [a], 5            ; ecx = -100 × 5 = -500

    mov eax, 1
    mov ebx, 0
    int 0x80

MULиIMULОсновное различие — обработка знаков:MULИнтерпретируются как беззнаковые числа,IMULИнтерпретируются как числа со знаком. Например, 0xFFFFFFFF — это 4294967295 в MUL и -1 в IMUL.


DIV — беззнаковое деление

DIVВыполняет беззнаковое деление, правила симметричны MUL:

Размер делителяДелимое (неявное)ЧастноеОстаток
1 байтAXALAH
2 байтаDX:AXAXDX
4 байтаEDX:EAXEAXEDX

Пример

; Путь к файлу: div_demo.asm
; Пример беззнакового деления (DIV)

section .data
    dividend dd 1000
    divisor dd 7
    quotient dd 0
    remainder dd 0

section .text
    global _start

_start:
    ; 32-битное деление: edx:eax / divisor
    ; Делимое сначала нужно расширить до EDX:EAX
    mov eax, [dividend]         ; eax = 1000
    mov edx, 0                  ; edx = 0 (очистить старшие 32 бита)
    div dword [divisor]         ; edx:eax / 7
    ; EAX = 142 (частное)
    ; EDX = 6 (остаток: 1000 = 142×7 + 6)

    mov [quotient], eax         ; частное = 142
    mov [remainder], edx        ; остаток = 6

    ; Пример деления байтов: 55 / 4
    mov ax, 55                  ; Делимое
    mov bl, 4                   ; Делитель
    div bl                      ; al = 13 (частное), ah = 3 (остаток)

    mov eax, 1
    mov ebx, 0
    int 0x80

Перед выполнением деления обязательно используйтеmov edx, 0илиxor edx, edxОчистите EDX! Если в EDX остались устаревшие данные, делимое будет неверным. Это самая распространённая ошибка при делении у начинающих.


IDIV - Деление со знаком

IDIVИспользуется для деления со знаком. Перед выполнением деления нужно использоватьCDQкоманда для знакового расширения EAX в EDX:EAX:

Пример

; пример деления со знаком IDIV

    ; Вычислить -100 / 3
    mov eax, -100               ; eax = -100
    cdq                         ; Знаковое расширение: edx:eax = -100
                                ; cdq копирует знаковый бит eax во все биты edx
    mov ebx, 3                  ; Делитель
    idiv ebx                    ; eax = -33 (частное), edx = -1 (остаток)
    ; Проверка: -100 = -33 × 3 + (-1)

ADC / SBB - операции с переносом/заимствованием

Используются дляарифметики с большими числами(данными, превышающими 32 бита):

Пример

; Путь к файлу: adc_sbb_demo.asm
; 64-битное сложение: сложение двух 64-битных чисел

section .data
    ; 64-битное число a = 0x00000001 FFFFFFFF
    a_low dd 0xFFFFFFFF
    a_high dd 0x00000001

    ; 64-битное число b = 0x00000000 00000005
    b_low dd 0x00000005
    b_high dd 0x00000000

    ; Результат
    result_low dd 0
    result_high dd 0

section .text
    global _start

_start:
    ; Сложить младшие 32 бита
    mov eax, [a_low]
    add eax, [b_low]            ; eax = 0xFFFFFFFF + 5 = 0x00000004
    ; CF = 1 (произошёл перенос!)
    mov [result_low], eax       ; result_low = 0x00000004

    ; Сложение старших 32 бит с переносом
    mov eax, [a_high]
    adc eax, [b_high]           ; adc = add + CF
    ; eax = 1 + 0 + 1(перенос) = 2
    mov [result_high], eax      ; result_high = 2

    ; Итоговый 64-битный результат: 0x00000002 00000004
    ; Проверка: 0x1FFFFFFF + 5 = 0x200000004

    ; Вычитание SBB аналогично (с заимствованием)
    mov eax, [a_low]
    sub eax, [b_low]            ; Вычитание младших 32 бит
    mov [result_low], eax

    mov eax, [a_high]
    sbb eax, [b_high]           ; sbb = sub - CF

    mov eax, 1
    mov ebx, 0
    int 0x80

Краткая справочная таблица арифметических инструкций

ИнструкцияФорматФункцияЗатрагиваемые флаги
ADDadd dest, srcdest = dest + srcCF, ZF, SF, OF, PF
SUBsub dest, srcdest = dest - srcCF, ZF, SF, OF, PF
INCinc destdest = dest + 1ZF, SF, OF, PF (не влияет на CF)
DECdec destdest = dest - 1ZF, SF, OF, PF (не влияет на CF)
MULmul srcБеззнаковое умножениеCF, OF
IMULimul srcЗнаковое умножениеCF, OF
DIVdiv srcБеззнаковое делениеНе определено (неопределённое)
IDIVidiv srcЗнаковое делениеНе определено (неопределённое)
ADCadc dest, srcdest = dest + src + CFCF, ZF, SF, OF, PF
SBBsbb dest, srcdest = dest - src - CFCF, ZF, SF, OF, PF
NEGneg destdest = -dest (отрицание)CF, ZF, SF, OF, PF
Другие расширения