文章目录

有符号除法

代码

#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​:为局部变量分配栈空间(abcdef等)。

  • 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:eaxedx填充 eax的符号位)。

    • 例如:

      • 如果 eax = 10(正数),则 edx = 0

      • 如果 eax = -10(负数),则 edx = 0xFFFFFFFF

  • idiv指令​​:

    • 计算 edx:eax / [b],商存储在 eax,余数存储在 edx

    • 10 / 3 = 3​(商 eax = 3,余数 edx = 1)。


​3. 负数除法(c / de / 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​:调试模式下检查栈是否平衡(防止栈溢出)。


​关键结论​

  1. ​有符号除法的汇编实现​​:

    • cdq​:符号扩展 eaxedx:eax(准备 64 位被除数)。

    • idiv​:计算 edx:eax / src,商存储在 eax,余数存储在 edx

  2. ​除法结果的截断方式​​:

    • C 语言的有符号除法 ​​向零截断​​(-10 / 3 = -310 / -3 = -3)。

    • 余数的符号与被除数相同(-10 % 3 = -110 % -3 = 1)。

  3. ​调试模式的影响​​:

    • 额外的栈填充 (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​:为局部变量分配栈空间(abcdunsigned_div1unsigned_div2等)。

  • rep stos​:在调试模式下用 0xCCCCCCCC填充栈(检测未初始化内存)。

  • @__CheckForDebuggerJustMyCode@4​:调试模式下的安全检查(MSVC 特有)。


​2. 无符号除法(a / bc / 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​:调试模式下检查栈是否平衡(防止栈溢出)。


​关键结论​

  1. ​无符号除法的汇编实现​​:

    • xor edx, edx​:清零 edx(无符号数的高位扩展是 0)。

    • div指令​​:

      • 被除数:edx:eax(64 位)。

      • 除数:[b][d](32 位)。

      • 商:eax,余数:edx

  2. ​与有符号除法的区别​​:

    • 有符号除法使用 idiv,并通过 cdq进行符号扩展。

    • 无符号除法使用 div,并通过 xor edx, edx清零高位。

  3. ​调试模式的影响​​:

    • 额外的栈填充 (0xCCCCCCCC) 和检查 (__RTC_CheckEsp) 会增加开销。

​优化对比​

如果开启编译器优化(如 -O2),未使用的变量会被删除,代码可能简化为:

main:
    xor eax, eax    ; return 0
    ret

但除法运算的逻辑在未优化模式下已经非常清晰。


​总结​

场景指令关键操作注意事项
无符号除法(32位)divxor edx, edx+ div [src]必须清零 edx
有符号除法(32位)idivcdq+ 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​:为局部变量分配栈空间(abcdefgh等)。

  • 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:eaxedx填充 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​:调试模式下检查栈是否平衡(防止栈溢出)。


​关键结论​

  1. ​有符号取模的实现​​:

    • 通过 idiv指令计算商和余数,​​余数存储在 edx​。

    • ​余数的符号与被除数相同​​(C 语言标准规定)。

  2. cdq的作用​​:

    • eax符号扩展到 edx:eax,确保 idiv正确处理负数。
  3. ​调试模式的影响​​:

    • 额外的栈填充 (0xCCCCCCCC) 和检查 (__RTC_CheckEsp) 会增加开销。
  4. ​与无符号取模的区别​​:

    • 无符号取模使用 div指令,且 xor edx, edx清零高位。

​总结​

运算指令关键操作余数规则
有符号取模(%idivcdq+ idiv [src]余数符号 = 被除数符号
无符号取模(%divxor 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​:为局部变量分配栈空间(abcdunsigned_mod1unsigned_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​:调试模式下检查栈是否平衡(防止栈溢出)。


​关键结论​

  1. ​无符号取模的实现​​:

    • 通过 div指令计算商和余数,​​余数存储在 edx​。

    • ​余数始终为非负数​​(无符号数的特性)。

  2. xor edx, edx的作用​​:

    • 清零 edx,确保 div正确处理无符号数。
  3. ​与有符号取模的区别​​:

    • 有符号取模使用 idiv,余数符号与被除数相同。

    • 无符号取模使用 div,余数始终为正。


​总结​

运算指令关键操作余数规则
无符号取模(%divxor edx, edx+ div [src]余数始终为非负数
有符号取模(%idivcdq+ idiv [src]余数符号 = 被除数符号
防止优化volatile+ printf强制生成实际指令避免常量折叠和死代码删除

通过这段汇编代码,可以清晰看到无符号取模运算的实现逻辑,以及 div指令如何同时计算商和余数。