文章目录
- 有符号除法
- **1. 函数入口和栈帧初始化**
- **2. 正数除法(`a / b`)**
- **3. 负数除法(`c / d`和 `e / f`)**
- **4. 函数返回**
- **关键结论**
- 无符号除法
- **1. 函数入口和栈帧初始化**
- **2. 无符号除法(`a / b`和 `c / d`)**
- **3. 函数返回**
- **关键结论**
- 有符号取模
- **1. 函数入口和栈帧初始化**
- **2. 有符号取模运算(`idiv`+ `edx`存储余数)**
- **3. 函数返回**
- **关键结论**
- 无符号取模
- **1. 函数入口和栈帧初始化**
- **2. 无符号取模运算(`div`+ `edx`存储余数)**
- **3. 函数返回**
- **关键结论**
有符号除法
代码
#include <stdio.h>
int main() {
// 正数除法
int a = 10, b = 3;
int signed_div1 = a / b; // 10 / 3 = 3
// 负数除法(向零截断)
int c = -10, d = 3;
int signed_div2 = c / d; // -10 / 3 = -3
int e = 10, f = -3;
int signed_div3 = e / f; // 10 / -3 = -3
return 0;
}
汇编
int main() {
00412590 push ebp
00412591 mov ebp,esp
00412593 sub esp,12Ch
00412599 push ebx
0041259A push esi
0041259B push edi
0041259C lea edi,[ebp-6Ch]
0041259F mov ecx,1Bh
004125A4 mov eax,0CCCCCCCCh
004125A9 rep stos dword ptr es:[edi]
004125AB mov ecx,offset _C66D3399_simple_cpp@cpp (041C008h)
004125B0 call @__CheckForDebuggerJustMyCode@4 (041131Bh)
004125B5 nop
// 正数除法
int a = 10, b = 3;
004125B6 mov dword ptr [a],0Ah
004125BD mov dword ptr [b],3
int signed_div1 = a / b; // 10 / 3 = 3
004125C4 mov eax,dword ptr [a]
004125C7 cdq
004125C8 idiv eax,dword ptr [b]
004125CB mov dword ptr [signed_div1],eax
// 负数除法(向零截断)
int c = -10, d = 3;
004125CE mov dword ptr [c],0FFFFFFF6h
004125D5 mov dword ptr [d],3
int signed_div2 = c / d; // -10 / 3 = -3
004125DC mov eax,dword ptr [c]
004125DF cdq
004125E0 idiv eax,dword ptr [d]
004125E3 mov dword ptr [signed_div2],eax
int e = 10, f = -3;
004125E6 mov dword ptr [e],0Ah
004125ED mov dword ptr [f],0FFFFFFFDh
int signed_div3 = e / f; // 10 / -3 = -3
004125F4 mov eax,dword ptr [e]
004125F7 cdq
004125F8 idiv eax,dword ptr [f]
004125FB mov dword ptr [signed_div3],eax
return 0;
004125FE xor eax,eax
}
00412600 pop edi
00412601 pop esi
00412602 pop ebx
00412603 add esp,12Ch
00412609 cmp ebp,esp
0041260B call __RTC_CheckEsp (041123Fh)
00412610 mov esp,ebp
00412612 pop ebp
00412613 ret
汇编代码分析(有符号除法,32位 x86,MSVC 编译器)
这段代码演示了 C 语言中的有符号整数除法,并通过汇编展示了 idiv指令的使用方式。以下是详细分析:
1. 函数入口和栈帧初始化
00412590 push ebp ; 保存旧的基址指针
00412591 mov ebp,esp ; 设置新的栈帧
00412593 sub esp,12Ch ; 分配栈空间 (0x12C 字节)
00412599 push ebx ; 保存寄存器
0041259A push esi
0041259B push edi
0041259C lea edi,[ebp-6Ch] ; 初始化栈空间起始地址
0041259F mov ecx,1Bh ; 循环次数 (27次)
004125A4 mov eax,0CCCCCCCCh ; 调试模式填充值 (0xCCCCCCCC)
004125A9 rep stos dword ptr es:[edi] ; 用 0xCCCCCCCC 填充栈
004125AB mov ecx,offset _C66D3399_simple_cpp@cpp (041C008h)
004125B0 call @__CheckForDebuggerJustMyCode@4 (041131Bh) ; 调试检查
004125B5 nop ; 空操作(对齐)
sub esp, 12Ch:为局部变量分配栈空间(a、b、c、d、e、f等)。
rep stos:在调试模式下用0xCCCCCCCC填充栈(检测未初始化内存)。
@__CheckForDebuggerJustMyCode@4:调试模式下的安全检查(MSVC 特有)。
2. 正数除法(a / b)
004125B6 mov dword ptr [a],0Ah ; a = 10 (0xA)
004125BD mov dword ptr [b],3 ; b = 3
004125C4 mov eax,dword ptr [a] ; eax = a
004125C7 cdq ; 扩展 eax -> edx:eax(符号扩展)
004125C8 idiv eax,dword ptr [b] ; eax = edx:eax / b,edx = 余数
004125CB mov dword ptr [signed_div1],eax ; signed_div1 = eax
cdq指令:将
eax符号扩展到edx:eax(edx填充eax的符号位)。例如:
如果
eax = 10(正数),则edx = 0。如果
eax = -10(负数),则edx = 0xFFFFFFFF。
idiv指令:计算
edx:eax / [b],商存储在eax,余数存储在edx。
10 / 3 = 3(商eax = 3,余数edx = 1)。
3. 负数除法(c / d和 e / f)
(1) c / d(-10 / 3 = -3)
004125CE mov dword ptr [c],0FFFFFFF6h ; c = -10 (补码: 0xFFFFFFF6)
004125D5 mov dword ptr [d],3 ; d = 3
004125DC mov eax,dword ptr [c] ; eax = c (-10)
004125DF cdq ; edx:eax = 0xFFFFFFFF:0xFFFFFFF6
004125E0 idiv eax,dword ptr [d] ; eax = -10 / 3 = -3
004125E3 mov dword ptr [signed_div2],eax ; signed_div2 = -3
cdq:eax = 0xFFFFFFF6(-10),所以edx = 0xFFFFFFFF(符号扩展)。
idiv:edx:eax = 0xFFFFFFFF:0xFFFFFFF6(即 -10)。-10 / 3 = -3(商eax = -3,余数edx = -1)。
(2) e / f(10 / -3 = -3)
004125E6 mov dword ptr [e],0Ah ; e = 10 (0xA)
004125ED mov dword ptr [f],0FFFFFFFDh ; f = -3 (补码: 0xFFFFFFFD)
004125F4 mov eax,dword ptr [e] ; eax = 10
004125F7 cdq ; edx:eax = 0:10
004125F8 idiv eax,dword ptr [f] ; eax = 10 / -3 = -3
004125FB mov dword ptr [signed_div3],eax ; signed_div3 = -3
cdq:eax = 10(正数),所以edx = 0。
idiv:edx:eax = 0:10(即 10)。10 / -3 = -3(商eax = -3,余数edx = 1)。
4. 函数返回
004125FE xor eax,eax ; eax = 0(返回值)
00412600 pop edi ; 恢复寄存器
00412601 pop esi
00412602 pop ebx
00412603 add esp,12Ch ; 释放栈空间
00412609 cmp ebp,esp ; 检查栈平衡
0041260B call __RTC_CheckEsp ; 调试模式栈检查
00412610 mov esp,ebp ; 恢复 esp
00412612 pop ebp ; 恢复 ebp
00412613 ret ; 返回
xor eax, eax:设置返回值0(C 语言的return 0)。
__RTC_CheckEsp:调试模式下检查栈是否平衡(防止栈溢出)。
关键结论
有符号除法的汇编实现:
cdq:符号扩展eax到edx:eax(准备 64 位被除数)。
idiv:计算edx:eax / src,商存储在eax,余数存储在edx。
除法结果的截断方式:
C 语言的有符号除法 向零截断(
-10 / 3 = -3,10 / -3 = -3)。余数的符号与被除数相同(
-10 % 3 = -1,10 % -3 = 1)。
调试模式的影响:
- 额外的栈填充 (
0xCCCCCCCC) 和检查 (__RTC_CheckEsp) 会增加开销。
- 额外的栈填充 (
优化对比
如果开启编译器优化(如 -O2),未使用的变量会被删除,代码可能简化为:
main:
xor eax, eax ; return 0
ret
但除法运算的逻辑在未优化模式下已经非常清晰。
无符号除法
代码
#include <stdio.h>
int main() {
unsigned int a = 10, b = 3;
unsigned int unsigned_div1 = a / b; // 10 / 3 = 3
unsigned int c = 15, d = 4;
unsigned int unsigned_div2 = c / d; // 15 / 4 = 3
return 0;
}
汇编
int main() {
00412590 push ebp
00412591 mov ebp,esp
00412593 sub esp,108h
00412599 push ebx
0041259A push esi
0041259B push edi
0041259C lea edi,[ebp-48h]
0041259F mov ecx,12h
004125A4 mov eax,0CCCCCCCCh
004125A9 rep stos dword ptr es:[edi]
004125AB mov ecx,offset _C66D3399_simple_cpp@cpp (041C008h)
004125B0 call @__CheckForDebuggerJustMyCode@4 (041131Bh)
004125B5 nop
unsigned int a = 10, b = 3;
004125B6 mov dword ptr [a],0Ah
004125BD mov dword ptr [b],3
unsigned int unsigned_div1 = a / b; // 10 / 3 = 3
004125C4 mov eax,dword ptr [a]
004125C7 xor edx,edx
004125C9 div eax,dword ptr [b]
004125CC mov dword ptr [unsigned_div1],eax
unsigned int c = 15, d = 4;
004125CF mov dword ptr [c],0Fh
004125D6 mov dword ptr [d],4
unsigned int unsigned_div2 = c / d; // 15 / 4 = 3
004125DD mov eax,dword ptr [c]
004125E0 xor edx,edx
004125E2 div eax,dword ptr [d]
004125E5 mov dword ptr [unsigned_div2],eax
return 0;
004125E8 xor eax,eax
}
汇编代码分析(无符号除法,32位 x86,MSVC 编译器)
这段代码演示了 C 语言中的无符号整数除法,并通过汇编展示了 div指令的使用方式。以下是详细分析:
1. 函数入口和栈帧初始化
00412590 push ebp ; 保存旧的基址指针
00412591 mov ebp,esp ; 设置新的栈帧
00412593 sub esp,108h ; 分配栈空间 (0x108 字节)
00412599 push ebx ; 保存寄存器
0041259A push esi
0041259B push edi
0041259C lea edi,[ebp-48h] ; 初始化栈空间起始地址
0041259F mov ecx,12h ; 循环次数 (18次)
004125A4 mov eax,0CCCCCCCCh ; 调试模式填充值 (0xCCCCCCCC)
004125A9 rep stos dword ptr es:[edi] ; 用 0xCCCCCCCC 填充栈
004125AB mov ecx,offset _C66D3399_simple_cpp@cpp (041C008h)
004125B0 call @__CheckForDebuggerJustMyCode@4 (041131Bh) ; 调试检查
004125B5 nop ; 空操作(对齐)
sub esp, 108h:为局部变量分配栈空间(a、b、c、d、unsigned_div1、unsigned_div2等)。
rep stos:在调试模式下用0xCCCCCCCC填充栈(检测未初始化内存)。
@__CheckForDebuggerJustMyCode@4:调试模式下的安全检查(MSVC 特有)。
2. 无符号除法(a / b和 c / d)
(1) a / b(10 / 3 = 3)
004125B6 mov dword ptr [a],0Ah ; a = 10 (0xA)
004125BD mov dword ptr [b],3 ; b = 3
004125C4 mov eax,dword ptr [a] ; eax = a
004125C7 xor edx,edx ; edx = 0(无符号扩展)
004125C9 div eax,dword ptr [b] ; eax = edx:eax / b(使用 div)
004125CC mov dword ptr [unsigned_div1],eax ; 存储商
xor edx, edx:- 在无符号除法前,必须将
edx清零(因为无符号数的高位扩展是 0)。
- 在无符号除法前,必须将
div指令:计算
edx:eax / [b],商存储在eax,余数存储在edx。
10 / 3 = 3(商eax = 3,余数edx = 1)。
(2) c / d(15 / 4 = 3)
004125CF mov dword ptr [c],0Fh ; c = 15 (0xF)
004125D6 mov dword ptr [d],4 ; d = 4
004125DD mov eax,dword ptr [c] ; eax = c
004125E0 xor edx,edx ; edx = 0
004125E2 div eax,dword ptr [d] ; eax = edx:eax / d
004125E5 mov dword ptr [unsigned_div2],eax ; 存储商
div指令:15 / 4 = 3(商eax = 3,余数edx = 3)。
3. 函数返回
004125E8 xor eax,eax ; eax = 0(返回值)
004125EA pop edi ; 恢复寄存器
004125EB pop esi
004125EC pop ebx
004125ED add esp,108h ; 释放栈空间
004125F3 cmp ebp,esp ; 检查栈平衡
004125F5 call __RTC_CheckEsp ; 调试模式栈检查
004125FA mov esp,ebp ; 恢复 esp
004125FC pop ebp ; 恢复 ebp
004125FD ret ; 返回
xor eax, eax:设置返回值0(C 语言的return 0)。
__RTC_CheckEsp:调试模式下检查栈是否平衡(防止栈溢出)。
关键结论
无符号除法的汇编实现:
xor edx, edx:清零edx(无符号数的高位扩展是 0)。
div指令:被除数:
edx:eax(64 位)。除数:
[b]或[d](32 位)。商:
eax,余数:edx。
与有符号除法的区别:
有符号除法使用
idiv,并通过cdq进行符号扩展。无符号除法使用
div,并通过xor edx, edx清零高位。
调试模式的影响:
- 额外的栈填充 (
0xCCCCCCCC) 和检查 (__RTC_CheckEsp) 会增加开销。
- 额外的栈填充 (
优化对比
如果开启编译器优化(如 -O2),未使用的变量会被删除,代码可能简化为:
main:
xor eax, eax ; return 0
ret
但除法运算的逻辑在未优化模式下已经非常清晰。
总结
| 场景 | 指令 | 关键操作 | 注意事项 |
|---|---|---|---|
| 无符号除法(32位) | div | xor edx, edx+ div [src] | 必须清零 edx |
| 有符号除法(32位) | idiv | cdq+ idiv [src] | cdq扩展符号位 |
| 防止优化 | volatile+ printf | 强制生成实际指令 | 避免常量折叠和死代码删除 |
通过这种方式,可以确保编译器生成 div指令,从而准确观察无符号除法的汇编行为。
有符号取模
代码
#include <stdio.h>
int main() {
// 正数取模
int a = 10, b = 3;
int signed_mod1 = a % b; // 10 % 3 = 1
// 被除数为负数(结果符号与被除数相同)
int c = -10, d = 3;
int signed_mod2 = c % d; // -10 % 3 = -1
// 除数为负数(结果符号与被除数相同)
int e = 10, f = -3;
int signed_mod3 = e % f; // 10 % -3 = 1
// 两个都为负数
int g = -10, h = -3;
int signed_mod4 = g % h; // -10 % -3 = -1
return 0;
}
汇编代码
int main() {
004144E0 push ebp
004144E1 mov ebp,esp
004144E3 sub esp,150h
004144E9 push ebx
004144EA push esi
004144EB push edi
004144EC lea edi,[ebp-90h]
004144F2 mov ecx,24h
004144F7 mov eax,0CCCCCCCCh
004144FC rep stos dword ptr es:[edi]
004144FE mov ecx,offset _C66D3399_simple_cpp@cpp (041C008h)
00414503 call @__CheckForDebuggerJustMyCode@4 (041131Bh)
00414508 nop
// 正数取模
int a = 10, b = 3;
00414509 mov dword ptr [a],0Ah
00414510 mov dword ptr [b],3
int signed_mod1 = a % b; // 10 % 3 = 1
00414517 mov eax,dword ptr [a]
0041451A cdq
0041451B idiv eax,dword ptr [b]
0041451E mov dword ptr [signed_mod1],edx
// 被除数为负数(结果符号与被除数相同)
int c = -10, d = 3;
00414521 mov dword ptr [c],0FFFFFFF6h
00414528 mov dword ptr [d],3
int signed_mod2 = c % d; // -10 % 3 = -1
0041452F mov eax,dword ptr [c]
00414532 cdq
00414533 idiv eax,dword ptr [d]
00414536 mov dword ptr [signed_mod2],edx
// 除数为负数(结果符号与被除数相同)
int e = 10, f = -3;
00414539 mov dword ptr [e],0Ah
00414540 mov dword ptr [f],0FFFFFFFDh
int signed_mod3 = e % f; // 10 % -3 = 1
00414547 mov eax,dword ptr [e]
0041454A cdq
0041454B idiv eax,dword ptr [f]
0041454E mov dword ptr [signed_mod3],edx
// 两个都为负数
int g = -10, h = -3;
00414551 mov dword ptr [g],0FFFFFFF6h
00414558 mov dword ptr [h],0FFFFFFFDh
int signed_mod4 = g % h; // -10 % -3 = -1
0041455F mov eax,dword ptr [g]
00414562 cdq
00414563 idiv eax,dword ptr [h]
00414566 mov dword ptr [signed_mod4],edx
return 0;
0041456C xor eax,eax
}
0041456E pop edi
0041456F pop esi
00414570 pop ebx
00414571 add esp,150h
00414577 cmp ebp,esp
00414579 call __RTC_CheckEsp (041123Fh)
0041457E mov esp,ebp
00414580 pop ebp
00414581 ret
汇编代码分析(有符号取模运算,32位 x86,MSVC 编译器)
这段代码演示了 C 语言中的有符号整数取模运算(%),并通过汇编展示了 idiv指令如何计算余数。以下是详细分析:
1. 函数入口和栈帧初始化
004144E0 push ebp ; 保存旧的基址指针
004144E1 mov ebp,esp ; 设置新的栈帧
004144E3 sub esp,150h ; 分配栈空间 (0x150 字节)
004144E9 push ebx ; 保存寄存器
004144EA push esi
004144EB push edi
004144EC lea edi,[ebp-90h] ; 初始化栈空间起始地址
004144F2 mov ecx,24h ; 循环次数 (36次)
004144F7 mov eax,0CCCCCCCCh ; 调试模式填充值 (0xCCCCCCCC)
004144FC rep stos dword ptr es:[edi] ; 用 0xCCCCCCCC 填充栈
004144FE mov ecx,offset _C66D3399_simple_cpp@cpp (041C008h)
00414503 call @__CheckForDebuggerJustMyCode@4 (041131Bh) ; 调试检查
00414508 nop ; 空操作(对齐)
sub esp, 150h:为局部变量分配栈空间(a、b、c、d、e、f、g、h等)。
rep stos:在调试模式下用0xCCCCCCCC填充栈(检测未初始化内存)。
@__CheckForDebuggerJustMyCode@4:调试模式下的安全检查(MSVC 特有)。
2. 有符号取模运算(idiv+ edx存储余数)
(1) a % b(10 % 3 = 1)
00414509 mov dword ptr [a],0Ah ; a = 10 (0xA)
00414510 mov dword ptr [b],3 ; b = 3
00414517 mov eax,dword ptr [a] ; eax = a
0041451A cdq ; 扩展 eax -> edx:eax(符号扩展)
0041451B idiv eax,dword ptr [b] ; eax = 商, edx = 余数
0041451E mov dword ptr [signed_mod1],edx ; signed_mod1 = edx (余数)
cdq指令:将
eax符号扩展到edx:eax(edx填充eax的符号位)。例如:
如果
eax = 10(正数),则edx = 0。如果
eax = -10(负数),则edx = 0xFFFFFFFF。
idiv指令:计算
edx:eax / [b],商存储在eax,余数存储在edx。
10 / 3 = 3(商eax = 3,余数edx = 1)。取模运算的本质:直接取
idiv计算后的余数(edx)。
(2) c % d(-10 % 3 = -1)
00414521 mov dword ptr [c],0FFFFFFF6h ; c = -10 (补码: 0xFFFFFFF6)
00414528 mov dword ptr [d],3 ; d = 3
0041452F mov eax,dword ptr [c] ; eax = c (-10)
00414532 cdq ; edx:eax = 0xFFFFFFFF:0xFFFFFFF6
00414533 idiv eax,dword ptr [d] ; eax = -3, edx = -1
00414536 mov dword ptr [signed_mod2],edx ; signed_mod2 = -1
idiv计算:-10 / 3 = -3(商eax = -3),余数edx = -1。余数的符号与被除数相同(
-10 % 3 = -1)。
(3) e % f(10 % -3 = 1)
00414539 mov dword ptr [e],0Ah ; e = 10 (0xA)
00414540 mov dword ptr [f],0FFFFFFFDh ; f = -3 (补码: 0xFFFFFFFD)
00414547 mov eax,dword ptr [e] ; eax = 10
0041454A cdq ; edx:eax = 0:10
0041454B idiv eax,dword ptr [f] ; eax = -3, edx = 1
0041454E mov dword ptr [signed_mod3],edx ; signed_mod3 = 1
idiv计算:10 / -3 = -3(商eax = -3),余数edx = 1。余数的符号与被除数相同(
10 % -3 = 1)。
(4) g % h(-10 % -3 = -1)
00414551 mov dword ptr [g],0FFFFFFF6h ; g = -10 (补码: 0xFFFFFFF6)
00414558 mov dword ptr [h],0FFFFFFFDh ; h = -3 (补码: 0xFFFFFFFD)
0041455F mov eax,dword ptr [g] ; eax = -10
00414562 cdq ; edx:eax = 0xFFFFFFFF:0xFFFFFFF6
00414563 idiv eax,dword ptr [h] ; eax = 3, edx = -1
00414566 mov dword ptr [signed_mod4],edx ; signed_mod4 = -1
idiv计算:-10 / -3 = 3(商eax = 3),余数edx = -1。余数的符号与被除数相同(
-10 % -3 = -1)。
3. 函数返回
0041456C xor eax,eax ; eax = 0(返回值)
0041456E pop edi ; 恢复寄存器
0041456F pop esi
00414570 pop ebx
00414571 add esp,150h ; 释放栈空间
00414577 cmp ebp,esp ; 检查栈平衡
00414579 call __RTC_CheckEsp ; 调试模式栈检查
0041457E mov esp,ebp ; 恢复 esp
00414580 pop ebp ; 恢复 ebp
00414581 ret ; 返回
xor eax, eax:设置返回值0(C 语言的return 0)。
__RTC_CheckEsp:调试模式下检查栈是否平衡(防止栈溢出)。
关键结论
有符号取模的实现:
通过
idiv指令计算商和余数,余数存储在edx。余数的符号与被除数相同(C 语言标准规定)。
cdq的作用:- 将
eax符号扩展到edx:eax,确保idiv正确处理负数。
- 将
调试模式的影响:
- 额外的栈填充 (
0xCCCCCCCC) 和检查 (__RTC_CheckEsp) 会增加开销。
- 额外的栈填充 (
与无符号取模的区别:
- 无符号取模使用
div指令,且xor edx, edx清零高位。
- 无符号取模使用
总结
| 运算 | 指令 | 关键操作 | 余数规则 |
|---|---|---|---|
有符号取模(%) | idiv | cdq+ idiv [src] | 余数符号 = 被除数符号 |
无符号取模(%) | div | xor edx, edx+ div [src] | 余数始终为正 |
| 防止优化 | volatile+ printf | 强制生成实际指令 | 避免常量折叠和死代码删除 |
通过这段汇编代码,可以清晰看到有符号取模运算的实现逻辑,以及 idiv指令如何同时计算商和余数。
无符号取模
代码
#include <stdio.h>
int main() {
unsigned int a = 10, b = 3;
unsigned int unsigned_mod1 = a % b; // 10 % 3 = 1
unsigned int c = 15, d = 4;
unsigned int unsigned_mod2 = c % d; // 15 % 4 = 3
return 0;
}
汇编
int main() {
004144E0 push ebp
004144E1 mov ebp,esp
004144E3 sub esp,108h
004144E9 push ebx
004144EA push esi
004144EB push edi
004144EC lea edi,[ebp-48h]
004144EF mov ecx,12h
004144F4 mov eax,0CCCCCCCCh
004144F9 rep stos dword ptr es:[edi]
004144FB mov ecx,offset _C66D3399_simple_cpp@cpp (041C008h)
00414500 call @__CheckForDebuggerJustMyCode@4 (041131Bh)
00414505 nop
unsigned int a = 10, b = 3;
00414506 mov dword ptr [a],0Ah
0041450D mov dword ptr [b],3
unsigned int unsigned_mod1 = a % b; // 10 % 3 = 1
00414514 mov eax,dword ptr [a]
00414517 xor edx,edx
00414519 div eax,dword ptr [b]
0041451C mov dword ptr [unsigned_mod1],edx
unsigned int c = 15, d = 4;
0041451F mov dword ptr [c],0Fh
00414526 mov dword ptr [d],4
unsigned int unsigned_mod2 = c % d; // 15 % 4 = 3
0041452D mov eax,dword ptr [c]
00414530 xor edx,edx
00414532 div eax,dword ptr [d]
00414535 mov dword ptr [unsigned_mod2],edx
return 0;
00414538 xor eax,eax
}
0041453A pop edi
0041453B pop esi
0041453C pop ebx
0041453D add esp,108h
00414543 cmp ebp,esp
00414545 call __RTC_CheckEsp (041123Fh)
0041454A mov esp,ebp
0041454C pop ebp
0041454D ret
汇编代码分析(无符号取模运算,32位 x86,MSVC 编译器)
这段代码演示了 C 语言中的无符号整数取模运算(%),并通过汇编展示了 div指令如何计算余数。以下是详细分析:
1. 函数入口和栈帧初始化
004144E0 push ebp ; 保存旧的基址指针
004144E1 mov ebp,esp ; 设置新的栈帧
004144E3 sub esp,108h ; 分配栈空间 (0x108 字节)
004144E9 push ebx ; 保存寄存器
004144EA push esi
004144EB push edi
004144EC lea edi,[ebp-48h] ; 初始化栈空间起始地址
004144EF mov ecx,12h ; 循环次数 (18次)
004144F4 mov eax,0CCCCCCCCh ; 调试模式填充值 (0xCCCCCCCC)
004144F9 rep stos dword ptr es:[edi] ; 用 0xCCCCCCCC 填充栈
004144FB mov ecx,offset _C66D3399_simple_cpp@cpp (041C008h)
00414500 call @__CheckForDebuggerJustMyCode@4 (041131Bh) ; 调试检查
00414505 nop ; 空操作(对齐)
sub esp, 108h:为局部变量分配栈空间(a、b、c、d、unsigned_mod1、unsigned_mod2等)。
rep stos:在调试模式下用0xCCCCCCCC填充栈(检测未初始化内存)。
@__CheckForDebuggerJustMyCode@4:调试模式下的安全检查(MSVC 特有)。
2. 无符号取模运算(div+ edx存储余数)
(1) a % b(10 % 3 = 1)
00414506 mov dword ptr [a],0Ah ; a = 10 (0xA)
0041450D mov dword ptr [b],3 ; b = 3
00414514 mov eax,dword ptr [a] ; eax = a
00414517 xor edx,edx ; edx = 0(无符号扩展)
00414519 div eax,dword ptr [b] ; eax = 商, edx = 余数
0041451C mov dword ptr [unsigned_mod1],edx ; unsigned_mod1 = edx (余数)
xor edx, edx:- 在无符号除法前,必须将
edx清零(因为无符号数的高位扩展是 0)。
- 在无符号除法前,必须将
div指令:计算
edx:eax / [b],商存储在eax,余数存储在edx。
10 / 3 = 3(商eax = 3,余数edx = 1)。取模运算的本质:直接取
div计算后的余数(edx)。
(2) c % d(15 % 4 = 3)
0041451F mov dword ptr [c],0Fh ; c = 15 (0xF)
00414526 mov dword ptr [d],4 ; d = 4
0041452D mov eax,dword ptr [c] ; eax = c
00414530 xor edx,edx ; edx = 0
00414532 div eax,dword ptr [d] ; eax = 3, edx = 3
00414535 mov dword ptr [unsigned_mod2],edx ; unsigned_mod2 = 3
div计算:15 / 4 = 3(商eax = 3),余数edx = 3。无符号取模的结果始终为非负数(
15 % 4 = 3)。
3. 函数返回
00414538 xor eax,eax ; eax = 0(返回值)
0041453A pop edi ; 恢复寄存器
0041453B pop esi
0041453C pop ebx
0041453D add esp,108h ; 释放栈空间
00414543 cmp ebp,esp ; 检查栈平衡
00414545 call __RTC_CheckEsp ; 调试模式栈检查
0041454A mov esp,ebp ; 恢复 esp
0041454C pop ebp ; 恢复 ebp
0041454D ret ; 返回
xor eax, eax:设置返回值0(C 语言的return 0)。
__RTC_CheckEsp:调试模式下检查栈是否平衡(防止栈溢出)。
关键结论
无符号取模的实现:
通过
div指令计算商和余数,余数存储在edx。余数始终为非负数(无符号数的特性)。
xor edx, edx的作用:- 清零
edx,确保div正确处理无符号数。
- 清零
与有符号取模的区别:
有符号取模使用
idiv,余数符号与被除数相同。无符号取模使用
div,余数始终为正。
总结
| 运算 | 指令 | 关键操作 | 余数规则 |
|---|---|---|---|
无符号取模(%) | div | xor edx, edx+ div [src] | 余数始终为非负数 |
有符号取模(%) | idiv | cdq+ idiv [src] | 余数符号 = 被除数符号 |
| 防止优化 | volatile+ printf | 强制生成实际指令 | 避免常量折叠和死代码删除 |
通过这段汇编代码,可以清晰看到无符号取模运算的实现逻辑,以及 div指令如何同时计算商和余数。
浙公网安备 33010602011771号