算术操作符 逆向汇编二
文章目录
- 有符号除法
- 代码
- 汇编
- **汇编代码分析(有符号除法,32位 x86,MSVC 编译器)**
- **1. 函数入口和栈帧初始化**
- **2. 正数除法(`a / b`)**
- **3. 负数除法(`c / d`和 `e / f`)**
- **(1) `c / d`(-10 / 3 = -3)**
- **(2) `e / f`(10 / -3 = -3)**
- **4. 函数返回**
- **关键结论**
- **优化对比**
- 无符号除法
- 代码
- 汇编
- **汇编代码分析(无符号除法,32位 x86,MSVC 编译器)**
- **1. 函数入口和栈帧初始化**
- **2. 无符号除法(`a / b`和 `c / d`)**
- **(1) `a / b`(10 / 3 = 3)**
- **(2) `c / d`(15 / 4 = 3)**
- **3. 函数返回**
- **关键结论**
- **优化对比**
- **总结**
- 有符号取模
- 代码
- 汇编代码
- **汇编代码分析(有符号取模运算,32位 x86,MSVC 编译器)**
- **1. 函数入口和栈帧初始化**
- **2. 有符号取模运算(`idiv`+ `edx`存储余数)**
- **(1) `a % b`(10 % 3 = 1)**
- **(2) `c % d`(-10 % 3 = -1)**
- **(3) `e % f`(10 % -3 = 1)**
- **(4) `g % h`(-10 % -3 = -1)**
- **3. 函数返回**
- **关键结论**
- **总结**
- 无符号取模
- 代码
- 汇编
- **汇编代码分析(无符号取模运算,32位 x86,MSVC 编译器)**
- **1. 函数入口和栈帧初始化**
- **2. 无符号取模运算(`div`+ `edx`存储余数)**
- **(1) `a % b`(10 % 3 = 1)**
- **(2) `c % d`(15 % 4 = 3)**
- **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 = -3int e = 10, f = -3;int signed_div3 = e / f; // 10 / -3 = -3return 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 0ret
但除法运算的逻辑在未优化模式下已经非常清晰。
无符号除法
代码
#include <stdio.h>int main() {unsigned int a = 10, b = 3;unsigned int unsigned_div1 = a / b; // 10 / 3 = 3unsigned int c = 15, d = 4;unsigned int unsigned_div2 = c / d; // 15 / 4 = 3return 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 0ret
但除法运算的逻辑在未优化模式下已经非常清晰。
总结
场景 | 指令 | 关键操作 | 注意事项 |
---|---|---|---|
无符号除法(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 = -1return 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 = 1unsigned int c = 15, d = 4;unsigned int unsigned_mod2 = c % d; // 15 % 4 = 3return 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
指令如何同时计算商和余数。