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

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

Для деления применяется команда div, к которой добавляется суффикс типа данных

  • divq: для деления 64-разрядных чисел

  • divl: для деления 32-разрядных чисел

  • divw: для деления 16-разрядных чисел

  • divb: для деления 8-разрядных чисел

Инструкция 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 имеет аналогичное действие, только в качестве операндов принимает числа со знаком.

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

.globl _start
.text
_start:
    movq $22, %rax
    movq $0, %rdx   # расширяем RDX нулем для корректности деления
    movq $5, %rcx
    divq %rcx       #  RAX = RAX / RCX
    # RAX =4 (результат), RDX = 2 (остаток)
    movq %rax, %rdi   # RDI = RAX = 4
    movq %rdx, %rdi   # RDI = RDX = 2
    movq $60, %rax  # RAX = 60
    syscall

Здесь делим 64-разрядное значение (регистр RAX) на другое 64-разрядное значение (регистр RCX). После деления в RAX помещается результат (в данном случае число 4), а в RDX - остаток (здесь число 2).

При этом в 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-разрядных числа со знаком:

.globl _start
.text
_start:
    movq $-5, %rcx

    movq $-22, %rax
    cqo     # расширяем регистр RDX знаковым битом из RAX 

    idivq %rcx         # RAX = RAX / RCX 
    # RAX =4 (результат), RDX = -2 (остаток)
    movq %rax, %rdi   # RDI = RAX = 4

    movq $60, %rax  # RAX = 60
    syscall

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