組語開發者常遇到「程式執行非法操作」錯誤,絕大多數源於 DIV/IDIV 除法溢位例外 (Interrupt 0 - Divide Error)。本文釐清 32 位元除法/乘法的暫存器慣例、正確初始化模式,並提供防錯範本。
x86 除法指令隱含使用 EDX:EAX 作為 64 位元被除數:
DIV ECX:EDX:EAX / ECX → 商存 EAX,餘數存 EDXIDIV ECX:有號版本,同樣用 EDX:EAXMUL ECX:EAX × ECX → 64 位元積存 EDX:EAXIMUL ECX:有號版本,同樣存 EDX:EAX (單運算元形式)關鍵點:若 EDX 未清零 (或未正確符號擴展),高 32 位含垃圾值,導致被除數遠大於除數,商超過 32 位元上限 → 除法溢位例外 (INT 0)。
MOV EAX, 1000
CWD ; 16 位元符號擴展 AX→DX:AX,不適用 32 位元!
DIV CX ; 16 位元除法,但 EAX 可能 > 65535,且 EDX 未清
CWD 只擴展 AX 到 DX:AX (16→32 位),不影響 EDX 高 16 位。32 位元除法必須用 CDQ (EAX→EDX:EAX) 或手動 XOR EDX, EDX。
XOR EDX, EDX ; 必須!清除高 32 位
MOV EAX, 6527363 ; 被除數
MOV ECX, 28732 ; 除數
DIV ECX ; EAX = 商, EDX = 餘數
此法適用所有無號 32 位元除法,簡單、安全、無分支。
MOV EAX, -6527363 ; 有號被除數
CDQ ; EAX 符號擴展至 EDX:EAX
MOV ECX, 28732
IDIV ECX ; EAX = 商, EDX = 餘數 (符號同被除數)
CDQ (Convert Double to Quad) 將 EAX 符號位複製到 EDX 全 32 位,正確構成 64 位有號被除數。
MUL ECX:EAX × ECX → EDX:EAX,EDX 為高 32 位積。IMUL ECX:有號版本,同樣結果存 EDX:EAX。IMUL 會隱含寫入 EDX,若後續程式碼依賴 EDX 舊值會出錯。IMUL EAX, ECX, 10 (EAX = ECX × 10),不影響 EDX,指令更短、效能更好。
; 無號 32 位元除法:EAX/ECX → EAX=商, EDX=餘數
UDiv32 MACRO
XOR EDX, EDX
DIV ECX
ENDM
; 有號 32 位元除法:EAX/ECX → EAX=商, EDX=餘數
IDiv32 MACRO
CDQ
IDIV ECX
ENDM
; 使用範例
MOV EAX, 12345678
MOV ECX, 1234
UDiv32
; EAX = 10004, EDX = 742
| 情境 | 錯誤原因 | 修正方法 |
|---|---|---|
| 除數為 0 | 邏輯錯誤導致 ECX=0 | 除法前檢查 JECXZ ErrorHandler |
| 商超過 32 位 | 被除數過大或 EDX 未清 | 確保 XOR EDX,EDX / CDQ,必要時用 64 位除法 (需多精度算法) |
| 有號除法結果錯誤 | 用 DIV 而非 IDIV,或未用 CDQ | 有號必須 CDQ + IDIV |
| MUL 後 EDX 被改寫 | 未預期 MUL 修改 EDX | 用三運算元 IMUL,或事先保存 EDX |
| 在迴圈中重複除法 | 每次迴圈都需重新清 EDX | 將 XOR EDX,EDX 放在迴圈內除法指令前 |
需多精度算法,典型做法為「長除法」模擬:
; 64 位無號除法:EDX:EAX / EBX → EAX=商, EDX=餘數
; 輸入:EDX:EAX = 被除數, EBX = 除數
; 輸出:EAX = 商, EDX = 餘數, EBX 不變
Div64:
PUSH ESI
MOV ESI, EAX ; 保存低 32 位
MOV EAX, EDX ; 高 32 位先除
XOR EDX, EDX
DIV EBX ; EAX = 高位商, EDX = 高位餘數
MOV ECX, EAX ; 保存高位商
MOV EAX, ESI ; 低 32 位
DIV EBX ; EAX = 低位商, EDX = 最終餘數
MOV EDX, ECX ; 合併商 (此處簡化,實際需 64 位商存雙暫存器)
POP ESI
RET
完整 64 位商需雙暫存器或記憶體儲存,實務建議呼叫編譯器內建 __aulldiv 或使用 64 位元模式 (RAX/RDX)。
XOR EDX, EDX + DIVCDQ + IDIVIMUL REG, REG, IMM,避免隱含改寫 EDX。