首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 組合語言

組語除法/乘法指令 (DIV/IDIV/MUL/IMUL) 正確用法與常見陷阱

     編輯:組合語言

前言

組語開發者常遇到「程式執行非法操作」錯誤,絕大多數源於 DIV/IDIV 除法溢位例外 (Interrupt 0 - Divide Error)。本文釐清 32 位元除法/乘法的暫存器慣例、正確初始化模式,並提供防錯範本。

核心規則:被除數雙精度、EDX 必須清零

x86 除法指令隱含使用 EDX:EAX 作為 64 位元被除數:

  • DIV ECX:EDX:EAX / ECX → 商存 EAX,餘數存 EDX
  • IDIV ECX:有號版本,同樣用 EDX:EAX
  • MUL ECX:EAX × ECX → 64 位元積存 EDX:EAX
  • IMUL ECX:有號版本,同樣存 EDX:EAX (單運算元形式)

關鍵點:若 EDX 未清零 (或未正確符號擴展),高 32 位含垃圾值,導致被除數遠大於除數,商超過 32 位元上限 → 除法溢位例外 (INT 0)。

錯誤與正確範例對照

❌ 錯誤:使用 CWD/CDQ 錯誤或未初始化 EDX


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。

✅ 正確:無條件清零 EDX (無號除法)


XOR EDX, EDX            ; 必須!清除高 32 位
MOV EAX, 6527363        ; 被除數
MOV ECX, 28732          ; 除數
DIV ECX                 ; EAX = 商, EDX = 餘數

此法適用所有無號 32 位元除法,簡單、安全、無分支。

✅ 正確:有號除法用 CDQ


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: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 / ECX) 實作

需多精度算法,典型做法為「長除法」模擬:


; 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 + DIV
  • 有號除法:CDQ + IDIV
  • 乘法:優先用三運算元 IMUL REG, REG, IMM,避免隱含改寫 EDX。
  • 除法前檢查除數非零,防止 INT 0 例外。
  • 將正確模式封裝為巨集/函數,建立肌肉記憶,杜絕「莫名其妙的非法操作」。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved