Divisão de Inteiros em Assembly NASM: As Instruções DIV e IDIV
As operações de divisão em Assembly (div e idiv) são mais rigorosas do que outras operações aritméticas, exigindo uma preparação cuidadosa dos operandos para funcionar corretamente. A arquitetura x86-64 oferece duas instruções primárias para essa finalidade:
div: Realiza a divisão de inteiros sem sinal (unsigned).idiv: Realiza a divisão de inteiros com sinal (signed).
Ambas as instruções aceitam um único operando explícito: o divisor. O dividendo (o número a ser dividido) é sempre um operando implícito, que deve ser preparado em um par de registradores. O resultado da operação são dois valores: o quociente e o resto.
O Mecanismo de Operação: A Regra do Dobro do Tamanho
A regra fundamental da divisão em x86-64 é que o dividendo deve ter o dobro do tamanho do divisor. A instrução espera que o dividendo já esteja corretamente posicionado no par de registradores apropriado antes de ser executada.
Tamanho do Divisor (source) | Dividendo (Implícito, 2n bits) | Operação Realizada | Quociente (n bits) | Resto (n bits) |
|---|---|---|---|---|
| 8 bits (byte) | AX (16 bits) | AX / source | AL | AH |
| 16 bits (word) | DX:AX (32 bits) | DX:AX / source | AX | DX |
| 32 bits (dword) | EDX:EAX (64 bits) | EDX:EAX / source | EAX | EDX |
| 64 bits (qword) | RDX:RAX (128 bits) | RDX:RAX / source | RAX | RDX |
Erro Comum: A falha mais comum é a exceção de divisão por zero (#DE), que ocorre se o divisor for zero, ou uma exceção de overflow, se o quociente for grande demais para caber no registrador de destino.
Exemplos e Preparação do Dividendo
Exemplo 1: Divisão de 16 bits por 8 bits (Linux)
Neste caso, o dividendo em AX (16 bits) já tem o dobro do tamanho do divisor em BL (8 bits).
global _start
section .text
_start:
xor rax, rax ; Zera RAX para garantir que AH (parte alta de AX) esteja limpa.
mov ax, 22 ; Dividendo de 16 bits em AX.
mov bl, 5 ; Divisor de 8 bits em BL.
div bl ; Executa AX / BL.
; Quociente (4) vai para AL, Resto (2) vai para AH.
movzx rdi, al ; Move o quociente para rdi para o código de saída.
mov rax, 60
syscallExemplo 1 Análogo para Windows:
global _start
section .text
_start:
xor rax, rax
mov ax, 22
mov bl, 5
div bl ; AL = 4 (quociente), AH = 2 (resto).
movzx rax, al ; Move o quociente para rax para o código de saída.
retExemplo 2: Divisão de 32 bits por 16 bits (Linux)
Para dividir um dividendo de 32 bits (EAX) por um divisor de 16 bits (BX), precisamos preparar o dividendo completo de 64 bits em EDX:EAX.
global _start
section .text
_start:
mov eax, 0x12003 ; Dividendo original de 32 bits.
; Abordagem padrão: zerar a parte alta do dividendo
xor edx, edx ; Prepara EDX:EAX como um número de 64 bits
; onde EDX=0 e EAX=0x12003.
mov bx, 0x10 ; Divisor de 16 bits.
div bx ; Executa EDX:EAX / BX.
; AX = 0x1200 (quociente), DX = 3 (resto).
movzx rdi, ax ; Move o quociente para rdi.
mov rax, 60
syscallEstendendo o Dividendo para Divisão
A forma correta de preparar o dividendo quando se quer dividir dois números do mesmo tamanho é crucial.
Para Divisão Sem Sinal (div): Extensão com Zeros
Preencha o registrador da parte alta com zeros. A forma mais eficiente é xor reg, reg.
Exemplo 3: Divisão de 64 bits por 64 bits (Sem Sinal, Linux)
global _start
section .text
_start:
mov rax, 22 ; Dividendo sem sinal.
mov rcx, 5 ; Divisor.
xor rdx, rdx ; Zera RDX para preparar o dividendo de 128 bits RDX:RAX.
div rcx ; Executa RDX:RAX / RCX.
; RAX = 4 (quociente), RDX = 2 (resto).
mov rdi, rax ; Move o quociente para o código de saída.
mov rax, 60
syscallPara Divisão Com Sinal (idiv): Extensão de Sinal
Propague o bit de sinal do dividendo para o registrador da parte alta. Para isso, use as instruções dedicadas: cbw, cwd, cdq, cqo.
Exemplo 4: Divisão de 64 bits por 64 bits (Com Sinal, Linux)
global _start
section .text
_start:
mov rax, -22 ; Dividendo com sinal.
mov rcx, 5 ; Divisor.
cqo ; Estende o sinal de RAX para RDX. RDX fica com -1.
idiv rcx ; Executa RDX:RAX / RCX.
; RAX = -4 (quociente), RDX = -2 (resto).
mov rdi, rax ; Move o quociente para o código de saída.
mov rax, 60
syscallExemplo 4 Análogo para Windows (Com Sinal):
global _start
section .text
_start:
mov rax, -22
mov rcx, 5
cqo ; Estende o sinal de RAX para RDX.
idiv rcx ; RAX = -4 (quociente), RDX = -2 (resto).
; O quociente já está em RAX, pronto para ser o código de saída.
retResumo
divé para divisão sem sinal;idivé para com sinal.- A regra fundamental é que o dividendo (implícito) deve ter o dobro do tamanho do divisor (explícito).
- O resultado da divisão é sempre um quociente e um resto, armazenados em registradores específicos.
- Antes de uma
divde operandos do mesmo tamanho, o registrador da parte alta do dividendo (ex:RDX) deve ser zerado (xor rdx, rdx). - Antes de uma
idivde operandos do mesmo tamanho, o dividendo deve ser estendido com sinal usando instruções comocqo.