DIV 指令概述
文章目录
- **DIV 指令:你的汇编除法小助手**
- **1. 这个计算器很挑食**
- **2. 三种尺寸可选(就像买衣服)**
- **3. 常见翻车现场**
- **4. 安全驾驶指南**
- **5. 为什么这么设计?**
- **一句话总结**
- **DIV 指令概述**
- **1. 字节除法(8位除数)**
- **2. 字除法(16位除数)**
- **3. 双字除法(32位除数)**
- **关键注意事项**
- **常见错误场景**
- 错误1:未初始化高位寄存器
- 错误2:忽略商溢出
- **总结表**
- 演示了 DIV 指令的三种用法
- 代码说明
- 1. **8位DIV演示**
- 2. **16位DIV演示**
- 3. **32位DIV演示**
- 4. **安全除法实践**
- 标志位说明
- 典型错误场景
- 总结表
- **1. 8位DIV指令演示(AX / 8位除数)**
- **场景**:
- **2. 16位DIV指令演示(DX:AX / 16位除数)**
- **场景**:
- **3. 32位DIV指令演示(EDX:EAX / 32位除数)**
- **场景**:
- **4. 安全除法示例(防除零和溢出)**
- **场景**:
- **标志位与异常行为**
- **总结表**
- **常见问题解决方案**
好的,我用更通俗易懂的方式来解释 DIV 指令的用法和注意事项,就像朋友聊天一样:
DIV 指令:你的汇编除法小助手
想象 DIV 指令就像是一个专门做除法的计算器,但它有几个怪癖你需要知道:
1. 这个计算器很挑食
- 只吃无符号数:给它负数它会懵圈(要用 IDIV 来处理有符号数)
- 讨厌除零:如果你让它除以0,它会直接"炸掉"(触发异常)
- 结果放的位置很讲究:商和余数总是放在固定的寄存器里
2. 三种尺寸可选(就像买衣服)
(1) 小号(8位版)
- 被除数:AX(16位)
- 除数:8位寄存器或内存
- 结果:
- 商:AL(8位)
- 余数:AH(8位)
- 坑点:如果商超过255(比如1000÷3≈333),计算器会"死机"
(2) 中号(16位版)
- 被除数:DX:AX(32位,像两个16位拼起来的)
- 除数:16位寄存器或内存
- 结果:
- 商:AX(16位)
- 余数:DX(16位)
- 使用技巧:
- 如果被除数小于65536,记得把DX清零
- 就像这样:
mov dx, 0 ; 清空高16位 mov ax, 50000 ; 被除数 mov bx, 100 ; 除数 div bx ; 结果:AX=500, DX=0
(3) 大号(32位版)
- 被除数:EDX:EAX(64位巨无霸)
- 除数:32位寄存器或内存
- 结果:
- 商:EAX(32位)
- 余数:EDX(32位)
- 重要提示:
- 处理大数时别忘了设置EDX
- 就像这样:
mov edx, 0 ; 清空高32位 mov eax, 1000000000 ; 被除数 mov ebx, 100000 ; 除数 div ebx ; 结果:EAX=10000, EDX=0
3. 常见翻车现场
(1) 忘记准备完整的被除数
mov ax, 100000 ; 想计算100000÷100
mov bx, 100
div bx ; 错误!应该先把DX设为1(因为100000 > 65535)
(2) 结果太大装不下
mov ax, 1000
mov bl, 1
div bl ; AL想存1000,但最大只能255,直接崩溃
(3) 最蠢的错误:除以零
mov eax, 100
mov ecx, 0
div ecx ; 电脑:你认真的吗?(╯°□°)╯︵ ┻━┻
4. 安全驾驶指南
-
每次使用前检查:
cmp divisor, 0 je error_handler ; 如果除数为零就跳转到错误处理
-
预估结果大小:
; 对于8位除法,检查AX是否≤255×除数 mov ax, 1000 mov bl, 5 cmp ax, 255*5 ; 255×5=1275 ja too_big ; 如果1000>1275就跳转 div bl
5. 为什么这么设计?
- 历史原因:早期CPU寄存器少,要省着用
- 效率考虑:硬件除法电路需要固定输入输出位置
- 安全机制:用异常而不是标志位来报错更可靠
一句话总结
DIV 就像个固执的老会计:
- 你必须把数字按它要求的格式摆好(AX/DX:AX/EDX:EAX)
- 它只接受无符号数
- 如果账目不对(除零或结果太大),它会直接摔账本(触发异常)
- 但只要遵守规则,它能快速准确地给你商和余数
希望这样解释更生动好记!实际使用时多检查边界条件就好。
以下是关于 DIV(无符号除法) 指令的详细说明,按操作数大小分类整理,并附注意事项和示例:
DIV 指令概述
功能:执行无符号除法,将被除数(AX、DX:AX 或 EDX:EAX)除以源操作数(除数),结果存储商和余数。
关键特性:
- 仅支持无符号运算。
- 若商超出目标寄存器容量或除数为0,触发
#DE
(除法错误)异常。 - 标志位(CF/OF/SF等)执行后为未定义状态。
1. 字节除法(8位除数)
操作码:F6 /6
操作数:DIV r/m8
寄存器布局:
被除数 | 除数 | 商 | 余数 | 商最大值 |
---|---|---|---|---|
AX | r/m8 | AL | AH | 255 |
操作过程:
- 被除数为
AX
(16位),除数为r/m8
(8位)。 - 计算
AX / r/m8
,商存入AL
,余数存入AH
。 - 若商 >
0FFH
(255),触发#DE
异常。
示例:
mov ax, 1000 ; AX = 1000 (03E8h)
mov bl, 3 ; BL = 3
div bl ; AL = 333 (商,但超过255!触发 #DE 异常)
2. 字除法(16位除数)
操作码:F7 /6
操作数:DIV r/m16
寄存器布局:
被除数 | 除数 | 商 | 余数 | 商最大值 |
---|---|---|---|---|
DX:AX | r/m16 | AX | DX | 65,535 |
操作过程:
- 被除数为
DX:AX
(32位,高16位在DX,低16位在AX),除数为r/m16
(16位)。 - 计算
(DX << 16 + AX) / r/m16
,商存入AX
,余数存入DX
。 - 若商 >
0FFFFH
(65,535),触发#DE
异常。
示例:
mov dx, 0 ; DX:AX = 00030000h (196,608)
mov ax, 30000h ;
mov bx, 2 ; BX = 2
div bx ; AX = 98,304 (商), DX = 0 (余数)
3. 双字除法(32位除数)
操作码:F7 /6
操作数:DIV r/m32
寄存器布局:
被除数 | 除数 | 商 | 余数 | 商最大值 |
---|---|---|---|---|
EDX:EAX | r/m32 | EAX | EDX | 232-1 |
操作过程:
- 被除数为
EDX:EAX
(64位,高32位在EDX,低32位在EAX),除数为r/m32
(32位)。 - 计算
(EDX << 32 + EAX) / r/m32
,商存入EAX
,余数存入EDX
。 - 若商 >
0FFFFFFFFH
(4,294,967,295),触发#DE
异常。
示例:
mov edx, 0 ; EDX:EAX = 0000000100000000h (4,294,967,296)
mov eax, 100000000h
mov ebx, 2 ; EBX = 2
div ebx ; EAX = 2,147,483,648 (商), EDX = 0 (余数)
关键注意事项
-
除数不能为0:
若除数为0,直接触发#DE
异常(等同于Linux下的SIGFPE
)。 -
商溢出检测:
DIV 通过比较商与目标寄存器容量判断溢出(如字节除法的商需 ≤255)。 -
被除数布局:
- 16位除法:被除数在
DX:AX
,需手动设置高位(DX)。 - 32位除法:被除数在
EDX:EAX
,高位(EDX)需清零(除非被除数 ≥232)。
- 16位除法:被除数在
-
符号位无关:
DIV 始终处理无符号数。若需有符号除法,应使用IDIV
指令。
常见错误场景
错误1:未初始化高位寄存器
mov ax, 1000 ; AX = 1000
mov bl, 3 ; BL = 3
div bl ; 正确:AX / BL
mov ax, 1000 ; AX = 1000
mov bx, 3 ; BX = 3
div bx ; 错误!未设置DX,导致被除数实际为 DX:AX = 0:1000
错误2:忽略商溢出
mov ax, 1000 ; AX = 1000 (03E8h)
mov bl, 1 ; BL = 1
div bl ; AL = 1000 > 255,触发 #DE 异常!
总结表
类型 | 被除数 | 除数 | 商 | 余数 | 最大商 |
---|---|---|---|---|---|
字节除法 | AX | r/m8 | AL | AH | 255 |
字除法 | DX:AX | r/m16 | AX | DX | 65,535 |
双字除法 | EDX:EAX | r/m32 | EAX | EDX | 4,294,967,295 |
通过合理设置被除数和除数,DIV 指令可高效完成无符号除法运算,但需严格防范除零和溢出问题。
演示了 DIV 指令的三种用法
以下是修改后的代码,演示了 DIV
指令的三种用法(8位、16位和32位无符号除法),并添加了详细的注释说明:
; 设置处理器模式和内存模型
.586 ; 使用 586 指令集
.model flat, stdcall ; 平坦内存模型,stdcall 调用约定
option casemap:none ; 区分大小写; 引入库文件
includelib kernel32.lib ; Windows API 库
includelib msvcrt.lib ; C 运行时库.data ; 数据段定义; 测试数据byteDivisor db 3 ; 8位除数wordDivisor dw 100 ; 16位除数dwordDivisor dd 50000 ; 32位除数; 结果存储byteQuotient db ?byteRemainder db ?wordQuotient dw ?wordRemainder dw ?dwordQuotient dd ?dwordRemainder dd ?; 错误检测divisionErrorOccurred db 0 ; 标记是否发生除法错误.code ; 代码段
main proc; ---------------------------; 1. 8位DIV指令演示 (AX / 8位除数); ---------------------------mov ax, 1000 ; AX = 1000 (被除数)mov bl, byteDivisor ; BL = 3 (除数)div bl ; AL = AX / BL (商), AH = AX % BL (余数); 预期结果: AL = 333 (但超过255,会触发 #DE 异常)mov byteQuotient, almov byteRemainder, ah; 捕获除法错误jnc no_error1mov divisionErrorOccurred, 1
no_error1:; ---------------------------; 2. 16位DIV指令演示 (DX:AX / 16位除数); ---------------------------mov dx, 0 ; DX:AX = 100000 (被除数高16位清零)mov ax, 100000 ; mov bx, wordDivisor ; BX = 100 (除数)div bx ; AX = (DX:AX) / BX (商), DX = 余数; 预期结果: AX = 1000, DX = 0mov wordQuotient, axmov wordRemainder, dx; ---------------------------; 3. 32位DIV指令演示 (EDX:EAX / 32位除数); ---------------------------mov edx, 0 ; EDX:EAX = 1000000000 (被除数高32位清零)mov eax, 1000000000 mov ebx, dwordDivisor ; EBX = 50000 (除数)div ebx ; EAX = (EDX:EAX) / EBX (商), EDX = 余数; 预期结果: EAX = 20000, EDX = 0mov dwordQuotient, eaxmov dwordRemainder, edx; ---------------------------; 4. 安全除法示例(防止除零和溢出); ---------------------------
safe_division:mov eax, 5000 ; 被除数mov ecx, 0 ; 尝试除零jecxz division_by_zero ; 检测除数为零div ecx ; 若继续执行会触发 #DEjmp division_donedivision_by_zero:mov divisionErrorOccurred, 1division_done:; ---------------------------; 程序退出; ---------------------------xor eax, eax ; 返回码 0ret
main endpend main
代码说明
1. 8位DIV演示
- 操作:
AX
(16位) 除以 8位除数 (BL
) - 关键点:
- 商必须 ≤ 255,否则触发
#DE
异常 - 示例中
1000 / 3 = 333
会溢出(333 > 255) - 实际应用中需预先检查:
if (AX / BL) > 255 then 错误
- 商必须 ≤ 255,否则触发
2. 16位DIV演示
- 操作:
DX:AX
(32位组合) 除以 16位除数 (BX
) - 关键点:
- 被除数高16位(DX)必须正确设置(若被除数 < 65536,DX=0)
- 商必须 ≤ 65535
- 示例
100000 / 100 = 1000
是安全的
3. 32位DIV演示
- 操作:
EDX:EAX
(64位组合) 除以 32位除数 (EBX
) - 关键点:
- 被除数高32位(EDX)需手动管理
- 商必须 ≤ 2³²-1
- 示例
1,000,000,000 / 50,000 = 20,000
合法
4. 安全除法实践
- 防除零:用
jecxz
检测除数为零 - 防溢出:预先计算商的范围(如8位除法时检查
AX ≤ (255 * divisor)
)
标志位说明
- DIV 执行后所有标志位(CF/OF等)状态未定义
- 需通过异常处理捕获错误(如
#DE
)
典型错误场景
-
未初始化高位寄存器
mov ax, 50000 ; 被除数 mov bx, 100 ; 除数 div bx ; 错误!未设置DX,实际被除数是 DX:AX = 0:50000
-
忽略商溢出
mov ax, 1000 ; AX = 1000 mov bl, 1 ; BL = 1 div bl ; AL = 1000 > 255 → #DE
-
除数为零
mov eax, 100 mov ecx, 0 div ecx ; 触发 #DE
总结表
类型 | 被除数 | 除数 | 商寄存器 | 余数寄存器 | 最大商 |
---|---|---|---|---|---|
8位除法 | AX | r/m8 | AL | AH | 255 |
16位除法 | DX:AX | r/m16 | AX | DX | 65,535 |
32位除法 | EDX:EAX | r/m32 | EAX | EDX | 4,294,967,295 |
此代码展示了如何安全使用DIV指令,并通过预检查避免常见错误。
以下是针对代码中每种 DIV
指令场景的详细解释,包括操作原理、预期结果和潜在问题:
1. 8位DIV指令演示(AX / 8位除数)
场景:
mov ax, 1000 ; AX = 1000 (03E8h)
mov bl, 3 ; BL = 3 (除数)
div bl ; AL = AX / BL, AH = AX % BL
- 操作原理:
- 被除数为
AX
(16位),除数为BL
(8位)。 - 计算
AX / BL
,商存入AL
,余数存入AH
。
- 被除数为
- 预期结果:
- 数学结果:
1000 / 3 = 333
余1
。 - 但
AL
最大只能存储255
(0FFh
),实际商333 > 255
。
- 数学结果:
- 实际行为:
- 触发
#DE
(除法错误)异常,因为商溢出。 - 若未处理异常,程序会崩溃。
- 触发
- 关键点:
- 必须预先检查:
if (AX) > (255 * divisor)
则拒绝运算。 - 8位除法适用于小数值(被除数 ≤ 65535,且商 ≤ 255)。
- 必须预先检查:
2. 16位DIV指令演示(DX:AX / 16位除数)
场景:
mov dx, 0 ; 高16位清零
mov ax, 100000 ; DX:AX = 000186A0h (100,000)
mov bx, 100 ; BX = 100 (除数)
div bx ; AX = 商, DX = 余数
- 操作原理:
- 被除数为
DX:AX
(32位组合),除数为BX
(16位)。 - 计算
(DX << 16 + AX) / BX
,商存入AX
,余数存入DX
。
- 被除数为
- 预期结果:
- 数学结果:
100000 / 100 = 1000
余0
。 AX = 1000
(03E8h
),DX = 0
。
- 数学结果:
- 关键点:
- 被除数 ≥ 65536 时,必须设置
DX
(如mov dx, 1
表示DX:AX = 1:86A0h = 100000
)。 - 商需 ≤ 65535(否则触发
#DE
)。
- 被除数 ≥ 65536 时,必须设置
3. 32位DIV指令演示(EDX:EAX / 32位除数)
场景:
mov edx, 0 ; 高32位清零
mov eax, 1000000000 ; EDX:EAX = 3B9ACA00h (1,000,000,000)
mov ebx, 50000 ; EBX = 50000 (除数)
div ebx ; EAX = 商, EDX = 余数
- 操作原理:
- 被除数为
EDX:EAX
(64位组合),除数为EBX
(32位)。 - 计算
(EDX << 32 + EAX) / EBX
,商存入EAX
,余数存入EDX
。
- 被除数为
- 预期结果:
- 数学结果:
1,000,000,000 / 50,000 = 20,000
余0
。 EAX = 20000
(4E20h
),EDX = 0
。
- 数学结果:
- 关键点:
- 被除数 ≥ 2³² 时,需设置
EDX
(如mov edx, 1
表示EDX:EAX = 1:00000000h ≈ 4.29亿
)。 - 商需 ≤ 4,294,967,295(否则触发
#DE
)。
- 被除数 ≥ 2³² 时,需设置
4. 安全除法示例(防除零和溢出)
场景:
mov eax, 5000 ; 被除数
mov ecx, 0 ; 尝试除零
jecxz division_by_zero ; 检测除数为零
div ecx ; 若执行会触发 #DE
jmp division_donedivision_by_zero:mov divisionErrorOccurred, 1
- 操作原理:
- 使用
jecxz
检测除数ECX
是否为零。 - 若除数为零,跳过
DIV
指令,标记错误。
- 使用
- 关键点:
- 防除零:必须显式检查除数是否为0(
DIV
不会自动处理)。 - 防溢出:需预先计算商是否超过目标寄存器容量(如8位除法时检查
AX ≤ 255 * divisor
)。
- 防除零:必须显式检查除数是否为0(
标志位与异常行为
- 标志位:
DIV
执行后所有标志位(CF/OF/SF等)状态未定义,不可依赖。 - 异常:
- 除零:触发
#DE
异常(Linux下为SIGFPE
)。 - 商溢出:同样触发
#DE
(如8位除法时商 > 255)。
- 除零:触发
总结表
场景 | 被除数范围 | 除数范围 | 商范围 | 注意事项 |
---|---|---|---|---|
8位DIV | AX: 0-65535 | 1-255 | AL: 0-255 | 检查 AX ≤ 255 * divisor |
16位DIV | DX:AX: 0-2³²-1 | 1-65535 | AX: 0-65535 | 大数需设置DX高位 |
32位DIV | EDX:EAX: 0-2⁶⁴-1 | 1-2³²-1 | EAX: 0-2³²-1 | 极大数需设置EDX高位 |
常见问题解决方案
-
如何避免除零?
test ebx, ebx ; 检查除数是否为零 jz error_label ; 跳转到错误处理 div ebx ; 安全执行
-
如何检测商溢出?
- 对8位除法:
cmp ax, 255 * divisor ja overflow_label div bl
- 对8位除法:
-
如何处理大被除数?
- 32位除法示例:
mov edx, 1 ; EDX:EAX = 1:00000000h (2³²) mov eax, 0 mov ebx, 2 div ebx ; EAX = 2³¹, EDX = 0
- 32位除法示例:
通过合理设置被除数、检查除数和商范围,可以安全使用 DIV
指令完成无符号除法运算。