Деление. Инструкции div и idiv

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

Для деления чисел в архитектуре x86-64 предназначены инструкции div и idiv. idiv делит два числа со знаком, а div - беззнаковые числа. Эти инструкции принимают следующие формы:

div reg8
div reg16
div reg32
div reg64

div mem8
div mem16
div mem32
div mem64

idiv reg8
idiv reg16
idiv reg32
idiv reg64

idiv mem8
idiv mem16
idiv mem32
idiv mem64

Инструкции принимают в качестве единственного операнда 8-, 16-, 32-, 64-разрядные регистры или 8-, 16-, 32-, 64-разрядные переменные.

Инструкция div делит два беззнаковых числа. Если операнд 8-разрядный, то div делит регистр AX на операнд, помещая частное в AL, а остаток (по модулю) в AH.

Если операнд 16-разрядный, то инструкция div делит 32-разрядное число в DX:AX на операнд, помещая частное в AX, а остаток в DX.

Если операнд 32-разрядный, div делит 64-битное число в EDX:EAX на операнд, помещая частное в EAX, а остаток в EDX.

И если операнд 64-разрядный, div делит 128-битное число в RDX:RAX на операнд, помещая частное в RAX, а остаток в RDX.

Инструкция idiv имеет аналогичное действие, только в качестве операндов принимает числа со знаком.

Пример деления на Linux:

global _start

section .text
_start:
    mov rax, 0  ; обнуляем регистр
    mov ax, 22  ; 16-разрядный регистр
    mov bl, 5   ; 8-разрядный регистр
    div bl      ; AX/BL = AL =4 (результат), AH = 2 (остаток)
    movzx rdi, al   ; RDI = 4
    mov rax, 60
    syscall 

Здесь делим 16-разрядное значение (регистр AX) на 8-разрядное значение (регистр BL). После деления в AL помещается результат (в данном случае число 4), а в AH - остаток (здесь число 2).

Аналогичный пример на Windows:

global _start

section .text
_start:
    mov rax, 0  ; обнуляем регистр
    mov ax, 22  ; 16-разрядный регистр
    mov bl, 5   ; 8-разрядный регистр
    div bl      ; AX/BL = AL =4 (результат), AH = 2 (остаток)
    movzx rax, al   ; RAX = 4
    ; mov rax, rdx    ; помещаем в RDI старшие 64 бита результата -  RDI = RDX = 3
    ret    

Чуть более сложный пример:

global _start
 
section .text
_start:
    mov eax, 0x12003  ; 32-разрядное число
    mov edx, eax      ; копируем данные из EAX и EDX
    shr edx, 16      ; сдвигаем биты вправо, чтобы старшие 16 бит из EAX оказались в DX
    mov bx, 0x10     ; 16-разрядный регистр
    div bx          ; AX:DX/BX, AX =0x1200 (результат), DX = 3 (остаток)
    movzx rdi, ax   ; rdi = 0x1200
    shr rdi, 8      ; rdi = 0x12 = 18

    mov rax, 60
    syscall

Здесь мы имеем 32-разрядное число в EAX. Чтобы его разделить на 16-разрядное число из BX, копируем EAX в EDX и затем сдвигаем скопированные биты на 16 разрядов вправо, чтобы старшие 16 бит из EAX оказались в DX.

При этом в x86-64 нельзя просто разделить два числа одинаковой разрядности, например, одно 8-разрядное на другое 8-разрядное. Если знаменатель представляет собой 8-битное значение, числитель должен быть 16-битным значением. Если же нужно разделить одно 8-битное значение без знака на другое, то необходимо дополнить числитель нулями до 16 бит, загрузив числитель в регистр AL, а затем переместив 0 в регистр AH. Отсутствие расширения AL до нуля перед выполнением div может привести к тому, что x86-64 выдаст некорректный результат.

Если нужно разделить два беззнаковых 16-разрядных числа, то надо расширить регистр AX (который содержит числитель) до регистра DX. Для этого достаточно загрузить 0 в регистр DX.

Если нужно разделить одно беззнаковое 32-битное значение на другое, перед делением надо расширить регистр EAX нулями до EDX (загрузив 0 в EDX).

И чтобы разделить одно 64-битное число на другое, перед делением нужно расширить RAX нулями до RDX (поместив 0 в RDX).

Перед делением целочисленных значений со знаком одинаковой разрядности с помощью idiv необходимо расширить знаком AL в AX (поместив знаковый бит числа в AH), AX в DX (знаковый бит в DX), EAX в EDX (знаковый бит в EDX) или RAX в RDX (знаковый бит в RDX). Для этого можно использовать следующие инструкции cbw, cwd, cdq или cqo

  • cbw: преобразует байт в AL в слово в AX через расширение знаком

  • cwd: преобразует 16-разрядное число в AX в 32-разрядное в DX:AX через расширение знаком

  • cdq: преобразует 32-разрядное число в EAX в 64-разрядное в EDX:EAX с помощью расширения знаком

  • cqo: преобразует 64-разрядное число в RAX в 128-разрядное в RDX:RAX через расширение знаком

  • cwde: преобразует 16-разрядное число в AX в 32-разрядное в EAX с помощью расширения знаком

  • cdqe: преобразует 32-разрядное число в EAX в 64-разрядное в RAX с помощью расширения знаком

Эти инструкции не принимают операндов. Отсутствие расширения знаком может привести к неверным результатам. Например, разделим два 64-разрядных числа со знаком:

global _start

section .text
_start:
    mov rax, 22  ; 64-разрядный регистр
    cqo          ; расширяем регистр RDX знаковым битом из RAX 
    mov rcx, 5   ; 64-разрядный регистр
    div rcx      ; RAX:RDX/RCX = RAX =4 (результат), RDX = 2 (остаток)
    mov rdi, rax
    mov rax, 60
    syscall 

Здесь делим значение из 64-разрядного регистра RAX на значение из другого 64-разрядного регистра RCX. Но так как это регистры одной разрядности, то расширяем регистр RDX знаком из RAX и фактически делим 128-разрядное значение из RDX:RAX на RCX. Результат будет в RAX, а остаток в RDX.

Аналогично работает инструкция idiv, только по отношению к числам со знаком:

global _start

section .text
_start:
    mov rax, -22  ; 64-разрядный регистр
    cqo          ; расширяем регистр RDX знаковым битом из RAX 
    mov rcx, 5   ; 64-разрядный регистр
    idiv rcx      ; RAX:RDX/RCX = RAX =-4 (результат), RDX = 2 (остаток)
    mov rdi, rax
    mov rax, 60
    syscall 

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