文章目录
-
- 1.演示代码
- 2.演示代码对应的汇编代码
- 3.主函数调用部分
- 完整的主函数栈帧建立
- [1. 无参数函数调用(完整分析)](#1. 无参数函数调用(完整分析))
- [2. 单参数函数调用(三种场景对比)](#2. 单参数函数调用(三种场景对比))
- [3. 双参数函数调用(完整分析)](#3. 双参数函数调用(完整分析))
- [4. 三参数函数调用(寄存器使用分析)](#4. 三参数函数调用(寄存器使用分析))
- [5. 15参数函数调用(完整栈操作)](#5. 15参数函数调用(完整栈操作))
- [6. 函数返回和栈帧销毁](#6. 函数返回和栈帧销毁)
- 关键技术细节总结:
-
- [1. **调用约定特征**](#1. 调用约定特征)
- [2. **寄存器使用模式**](#2. 寄存器使用模式)
- [3. **内存布局**](#3. 内存布局)
- [4. **调试特征**](#4. 调试特征)
- [5. **性能优化点**](#5. 性能优化点)
- 传递参数和不传递参数有啥区别,如果从堆栈的角度传递参数和不传递参数有什么区别
-
- [1. 无参数函数调用的堆栈变化](#1. 无参数函数调用的堆栈变化)
- [2. 有参数函数调用的堆栈变化](#2. 有参数函数调用的堆栈变化)
- [3. 关键区别对比](#3. 关键区别对比)
- [4. 具体堆栈布局对比](#4. 具体堆栈布局对比)
- [5. 从函数内部视角看区别](#5. 从函数内部视角看区别)
- [6. 性能影响分析](#6. 性能影响分析)
- [7. 实际堆栈示例对比](#7. 实际堆栈示例对比)
- 总结
- [4. 函数内部是如何使用这些传入的参数的呢](#4. 函数内部是如何使用这些传入的参数的呢)
-
- [1. 单个参数函数 `funcOneParam(int a)`](#1. 单个参数函数
funcOneParam(int a)
) - [2. 两个参数函数 `funcTwoParams(int a, int b)`](#2. 两个参数函数
funcTwoParams(int a, int b)
) - [3. 三个参数函数 `funcThreeParams(int a, int b, int c)`](#3. 三个参数函数
funcThreeParams(int a, int b, int c)
) - [4. 多个参数函数 `funcManyParams` (15个参数)](#4. 多个参数函数
funcManyParams
(15个参数)) - 关键观察:
- [1. 单个参数函数 `funcOneParam(int a)`](#1. 单个参数函数
1.演示代码
cpp
#include <iostream>
// 无参数函数
void funcNoParams() {
std::cout << "无参数函数被调用" << std::endl;
}
// 单个参数函数
void funcOneParam(int a) {
std::cout << "单参数函数: " << a << std::endl;
}
// 两个参数函数
void funcTwoParams(int a, int b) {
std::cout << "双参数函数: " << a << ", " << b << std::endl;
}
// 三个参数函数
void funcThreeParams(int a, int b, int c) {
std::cout << "三参数函数: " << a << ", " << b << ", " << c << std::endl;
}
// 多个参数函数(15个参数)
void funcManyParams(int a1, int a2, int a3, int a4, int a5,
int a6, int a7, int a8, int a9, int a10,
int a11, int a12, int a13, int a14, int a15) {
std::cout << "15参数函数调用:" << std::endl;
std::cout << "前5个参数: " << a1 << ", " << a2 << ", " << a3 << ", " << a4 << ", " << a5 << std::endl;
std::cout << "中5个参数: " << a6 << ", " << a7 << ", " << a8 << ", " << a9 << ", " << a10 << std::endl;
std::cout << "后5个参数: " << a11 << ", " << a12 << ", " << a13 << ", " << a14 << ", " << a15 << std::endl;
}
int main() {
//std::cout << "=== 函数参数传递对比演示 ===" << std::endl;
// 演示无参数函数调用
//std::cout << "\n--- 无参数函数调用 ---" << std::endl;
funcNoParams();
// 演示单个参数函数调用
//std::cout << "\n--- 单参数函数调用 ---" << std::endl;
int x = 10;
funcOneParam(x); // 使用变量
funcOneParam(20); // 使用字面量
funcOneParam(x + 5); // 使用表达式
// 演示两个参数函数调用
//std::cout << "\n--- 双参数函数调用 ---" << std::endl;
int y = 30;
funcTwoParams(x, y); // 使用两个变量
funcTwoParams(40, 50); // 使用两个字面量
funcTwoParams(x, y * 2); // 混合使用变量和表达式
// 演示三个参数函数调用
//std::cout << "\n--- 三参数函数调用 ---" << std::endl;
int z = 60;
funcThreeParams(x, y, z); // 使用三个变量
funcThreeParams(70, 80, 90); // 使用三个字面量
// 演示多个参数函数调用(使用变量)
//std::cout << "\n--- 多参数函数调用(使用变量)---" << std::endl;
int var1 = 100, var2 = 200, var3 = 300, var4 = 400, var5 = 500;
int var6 = 600, var7 = 700, var8 = 800, var9 = 900, var10 = 1000;
int var11 = 1100, var12 = 1200, var13 = 1300, var14 = 1400, var15 = 1500;
funcManyParams(var1, var2, var3, var4, var5,
var6, var7, var8, var9, var10,
var11, var12, var13, var14, var15);
return 0;
}
2.演示代码对应的汇编代码
3.主函数调用部分
cpp
int main() {
00412444 in al,dx
00412445 cwde
00412446 add dword ptr [eax],eax
00412448 add byte ptr [ebx+56h],dl
0041244B push edi
0041244C lea edi,[ebp-0D8h]
00412452 mov ecx,36h
00412457 mov eax,0CCCCCCCCh
0041245C rep stos dword ptr es:[edi]
0041245E mov ecx,offset _C66D3399_simple_cpp@cpp (0420077h)
00412463 call @__CheckForDebuggerJustMyCode@4 (04113C5h)
00412468 nop
//std::cout << "=== 函数参数传递对比演示 ===" << std::endl;
// 演示无参数函数调用
//std::cout << "\n--- 无参数函数调用 ---" << std::endl;
funcNoParams();
00412469 call funcNoParams (0411532h)
0041246E nop
// 演示单个参数函数调用
//std::cout << "\n--- 单参数函数调用 ---" << std::endl;
int x = 10;
0041246F mov dword ptr [x],0Ah
funcOneParam(x); // 使用变量
00412476 mov eax,dword ptr [x]
00412479 push eax
0041247A call funcOneParam (041152Dh)
0041247F add esp,4
funcOneParam(20); // 使用字面量
00412482 push 14h
00412484 call funcOneParam (041152Dh)
00412489 add esp,4
funcOneParam(x + 5); // 使用表达式
0041248C mov eax,dword ptr [x]
0041248F add eax,5
00412492 push eax
00412493 call funcOneParam (041152Dh)
00412498 add esp,4
// 演示两个参数函数调用
//std::cout << "\n--- 双参数函数调用 ---" << std::endl;
int y = 30;
0041249B mov dword ptr [y],1Eh
funcTwoParams(x, y); // 使用两个变量
004124A2 mov eax,dword ptr [y]
004124A5 push eax
004124A6 mov ecx,dword ptr [x]
004124A9 push ecx
004124AA call funcTwoParams (0411528h)
004124AF add esp,8
funcTwoParams(40, 50); // 使用两个字面量
004124B2 push 32h
004124B4 push 28h
004124B6 call funcTwoParams (0411528h)
004124BB add esp,8
funcTwoParams(x, y * 2); // 混合使用变量和表达式
004124BE mov eax,dword ptr [y]
004124C1 shl eax,1
004124C3 push eax
004124C4 mov ecx,dword ptr [x]
004124C7 push ecx
004124C8 call funcTwoParams (0411528h)
004124CD add esp,8
// 演示三个参数函数调用
//std::cout << "\n--- 三参数函数调用 ---" << std::endl;
int z = 60;
004124D0 mov dword ptr [z],3Ch
funcThreeParams(x, y, z); // 使用三个变量
004124D7 mov eax,dword ptr [z]
004124DA push eax
004124DB mov ecx,dword ptr [y]
004124DE push ecx
004124DF mov edx,dword ptr [x]
004124E2 push edx
004124E3 call funcThreeParams (041153Ch)
004124E8 add esp,0Ch
funcThreeParams(70, 80, 90); // 使用三个字面量
004124EB push 5Ah
004124ED push 50h
004124EF push 46h
004124F1 call funcThreeParams (041153Ch)
004124F6 add esp,0Ch
// 演示多个参数函数调用(使用变量)
//std::cout << "\n--- 多参数函数调用(使用变量)---" << std::endl;
int var1 = 100, var2 = 200, var3 = 300, var4 = 400, var5 = 500;
004124F9 mov dword ptr [var1],64h
00412500 mov dword ptr [var2],0C8h
00412507 mov dword ptr [var3],12Ch
0041250E mov dword ptr [var4],190h
00412515 mov dword ptr [var5],1F4h
int var6 = 600, var7 = 700, var8 = 800, var9 = 900, var10 = 1000;
0041251C mov dword ptr [var6],258h
00412523 mov dword ptr [var7],2BCh
0041252A mov dword ptr [var8],320h
00412531 mov dword ptr [var9],384h
0041253B mov dword ptr [var10],3E8h
int var11 = 1100, var12 = 1200, var13 = 1300, var14 = 1400, var15 = 1500;
00412545 mov dword ptr [var11],44Ch
0041254F mov dword ptr [var12],4B0h
00412559 mov dword ptr [var13],514h
00412563 mov dword ptr [var14],578h
0041256D mov dword ptr [var15],5DCh
funcManyParams(var1, var2, var3, var4, var5,
00412577 mov eax,dword ptr [var15]
0041257D push eax
0041257E mov ecx,dword ptr [var14]
00412584 push ecx
00412585 mov edx,dword ptr [var13]
0041258B push edx
0041258C mov eax,dword ptr [var12]
00412592 push eax
00412593 mov ecx,dword ptr [var11]
00412599 push ecx
0041259A mov edx,dword ptr [var10]
004125A0 push edx
004125A1 mov eax,dword ptr [var9]
004125A7 push eax
004125A8 mov ecx,dword ptr [var8]
004125AB push ecx
004125AC mov edx,dword ptr [var7]
004125AF push edx
004125B0 mov eax,dword ptr [var6]
004125B3 push eax
004125B4 mov ecx,dword ptr [var5]
004125B7 push ecx
004125B8 mov edx,dword ptr [var4]
004125BB push edx
004125BC mov eax,dword ptr [var3]
004125BF push eax
004125C0 mov ecx,dword ptr [var2]
004125C3 push ecx
004125C4 mov edx,dword ptr [var1]
004125C7 push edx
004125C8 call funcManyParams (0411537h)
004125CD add esp,3Ch
var6, var7, var8, var9, var10,
var11, var12, var13, var14, var15);
return 0;
004125D0 xor eax,eax
}
004125D2 pop edi
004125D3 pop esi
004125D4 pop ebx
004125D5 add esp,198h
004125DB cmp ebp,esp
004125DD call __RTC_CheckEsp (04112C1h)
004125E2 mov esp,ebp
004125E4 pop ebp
004125E5 ret
您说得对,我的分析确实不够完整。让我提供更全面的分析,包括函数调用前后的完整栈帧管理和所有细节:
完整的主函数栈帧建立
cpp
int main() {
00412444 in al,dx
00412445 cwde
00412446 add dword ptr [eax],eax
00412448 add byte ptr [ebx+56h],dl
0041244B push edi
0041244C lea edi,[ebp-0D8h]
00412452 mov ecx,36h
00412457 mov eax,0CCCCCCCCh
0041245C rep stos dword ptr es:[edi]
0041245E mov ecx,offset _C66D3399_simple_cpp@cpp (0420077h)
00412463 call @__CheckForDebuggerJustMyCode@4 (04113C5h)
00412468 nop
详细分析:
push edi
- 保存EDI寄存器lea edi,[ebp-0D8h]
- 计算局部变量区域的起始地址mov ecx,36h
- 设置循环次数(54次)mov eax,0CCCCCCCCh
- 调试模式的填充值(未初始化内存)rep stos
- 用0xCC填充局部变量空间(调试特征)- 调用调试检查函数
1. 无参数函数调用(完整分析)
cpp
funcNoParams();
00412469 call funcNoParams (0411532h)
0041246E nop
完整调用过程:
call
指令:将返回地址(0041246E)压栈,跳转到函数- 函数执行完毕后返回到
nop
指令 - 无参数,无栈调整
2. 单参数函数调用(三种场景对比)
场景1:变量传递
cpp
int x = 10;
0041246F mov dword ptr [x],0Ah
funcOneParam(x);
00412476 mov eax,dword ptr [x] ; 从内存加载x到eax
00412479 push eax ; 参数压栈
0041247A call funcOneParam (041152Dh)
0041247F add esp,4 ; 调用者清理栈(1参数=4字节)
场景2:字面量传递
cpp
funcOneParam(20);
00412482 push 14h ; 直接压入字面量20(0x14)
00412484 call funcOneParam (041152Dh)
00412489 add esp,4
场景3:表达式传递
cpp
funcOneParam(x + 5);
0041248C mov eax,dword ptr [x] ; 加载x
0041248F add eax,5 ; 计算表达式(x+5)
00412492 push eax ; 结果压栈
00412493 call funcOneParam (041152Dh)
00412498 add esp,4
3. 双参数函数调用(完整分析)
cpp
int y = 30;
0041249B mov dword ptr [y],1Eh
funcTwoParams(x, y);
004124A2 mov eax,dword ptr [y] ; 加载第二个参数y
004124A5 push eax ; 先压入y(从右向左)
004124A6 mov ecx,dword ptr [x] ; 加载第一个参数x
004124A9 push ecx ; 后压入x
004124AA call funcTwoParams (0411528h)
004124AF add esp,8 ; 清理8字节(2参数)
4. 三参数函数调用(寄存器使用分析)
cpp
int z = 60;
004124D0 mov dword ptr [z],3Ch
funcThreeParams(x, y, z);
004124D7 mov eax,dword ptr [z] ; 使用eax加载第三个参数
004124DA push eax ; 压入z
004124DB mov ecx,dword ptr [y] ; 使用ecx加载第二个参数
004124DE push ecx ; 压入y
004124DF mov edx,dword ptr [x] ; 使用edx加载第一个参数
004124E2 push edx ; 压入x
004124E3 call funcThreeParams (041153Ch)
004124E8 add esp,0Ch ; 清理12字节(3参数)
5. 15参数函数调用(完整栈操作)
cpp
funcManyParams(var1, var2, var3, var4, var5, var6, var7, var8, var9, var10, var11, var12, var13, var14, var15);
00412577 mov eax,dword ptr [var15] ; 第15个参数
0041257D push eax
0041257E mov ecx,dword ptr [var14] ; 第14个参数
00412584 push ecx
; ... 中间13个参数的push操作 ...
004125C4 mov edx,dword ptr [var1] ; 第1个参数
004125C7 push edx
004125C8 call funcManyParams (0411537h)
004125CD add esp,3Ch ; 清理60字节(15×4字节)
6. 函数返回和栈帧销毁
assembly
return 0;
004125D0 xor eax,eax ; 返回值0
}
004125D2 pop edi ; 恢复EDI
004125D3 pop esi ; 恢复ESI
004125D4 pop ebx ; 恢复EBX
004125D5 add esp,198h ; 调整栈指针,释放局部变量空间
004125DB cmp ebp,esp ; 检查栈平衡
004125DD call __RTC_CheckEsp (04112C1h) ; 运行时栈检查
004125E2 mov esp,ebp ; 恢复栈指针
004125E4 pop ebp ; 恢复基址指针
004125E5 ret ; 返回
关键技术细节总结:
1. 调用约定特征
- cdecl调用约定:参数从右向左压栈,调用者清理栈
- 栈清理方式 :
add esp, N
(N=参数个数×4)
2. 寄存器使用模式
- 参数传递:全部使用栈,未使用寄存器传参
- 临时存储:eax, ecx, edx 用于加载参数值
- 保存寄存器:ebx, esi, edi 在函数开始时保存
3. 内存布局
c
栈顶 → [参数n] ... [参数2] [参数1] [返回地址] [保存的ebp] [局部变量]
4. 调试特征
0xCCCCCCCC
填充:未初始化内存的调试标记__CheckForDebuggerJustMyCode
:调试器检查__RTC_CheckEsp
:运行时栈检查
5. 性能优化点
- 字面量直接压栈,避免内存加载
- 表达式在压栈前计算完成
- 寄存器重用优化(不同参数使用不同寄存器)
这个完整的分析展示了x86架构下C++函数调用的完整机制,包括参数传递、栈管理和调试支持的所有细节。
传递参数和不传递参数有啥区别,如果从堆栈的角度传递参数和不传递参数有什么区别
从堆栈角度分析,传递参数和不传递参数有本质区别。让我详细对比:
1. 无参数函数调用的堆栈变化
c
funcNoParams();
00412469 call funcNoParams (0411532h)
0041246E nop
堆栈变化:
c
调用前堆栈: [返回地址] [保存的ebp] [局部变量] ...
↓ call指令执行
调用中堆栈: [新的返回地址(0041246E)] [旧的返回地址] [保存的ebp] [局部变量] ...
↑
栈顶下移4字节,只压入返回地址
2. 有参数函数调用的堆栈变化
单参数例子:
c
funcOneParam(x);
00412476 mov eax,dword ptr [x]
00412479 push eax ; 参数压栈
0041247A call funcOneParam ; 返回地址压栈
0041247F add esp,4 ; 清理参数
堆栈变化:
c
调用前堆栈: [返回地址] [保存的ebp] [局部变量] ...
↓ push eax
[参数x] [返回地址] [保存的ebp] [局部变量] ...
↓ call指令执行
[新的返回地址] [参数x] [旧的返回地址] [保存的ebp] [局部变量] ...
↑
栈顶下移8字节(参数4+返回地址4)
3. 关键区别对比
方面 | 无参数调用 | 有参数调用 |
---|---|---|
栈空间占用 | 只压入返回地址(4字节) | 返回地址+所有参数大小 |
栈指针调整 | call前后esp-4 | push参数时esp-N,call时esp-4,清理时esp+N |
内存访问 | 无额外内存访问 | 需要访问参数所在内存位置 |
执行时间 | 较短 | 较长(与参数数量成正比) |
堆栈布局 | 简单 | 复杂,需要维护参数区 |
4. 具体堆栈布局对比
无参数调用时的堆栈帧:
c
高地址 → [调用者的栈帧]
[main函数的返回地址]
[保存的ebp] ← ebp指向这里
[局部变量x,y,z...] ← esp指向这里
低地址 → [空栈空间]
调用funcNoParams时:
c
高地址 → [调用者的栈帧]
[main函数的返回地址]
[保存的ebp]
[局部变量x,y,z...]
[funcNoParams返回地址(0041246E)] ← esp指向这里
低地址 → [空栈空间]
有参数调用时的堆栈帧(以funcTwoParams为例):
调用前:
c
高地址 → [调用者的栈帧]
[main函数的返回地址]
[保存的ebp]
[局部变量x,y,z...] ← esp指向这里
低地址 → [空栈空间]
调用过程中:
c
高地址 → [调用者的栈帧]
[main函数的返回地址]
[保存的ebp]
[局部变量x,y,z...]
[参数y] ← push eax (y)
[参数x] ← push ecx (x)
[返回地址] ← call指令,esp指向这里
低地址 → [空栈空间]
5. 从函数内部视角看区别
无参数函数内部访问:
cpp
void funcNoParams() {
// 只能访问自己的局部变量
// 无法通过参数访问调用者的数据
}
有参数函数内部访问:
cpp
void funcTwoParams(int a, int b) {
// 可以通过ebp+8访问第一个参数a
// 可以通过ebp+12访问第二个参数b
// 汇编中通常:a=[ebp+8], b=[ebp+12]
}
函数内部的堆栈视角:
c
高地址 → [调用者的栈帧]
[返回地址] ← ebp+4
[保存的ebp] ← ebp指向这里
[局部变量] ← ebp-4, ebp-8, ...
[参数1] ← ebp+8 (第一个参数)
[参数2] ← ebp+12 (第二个参数)
[参数...] ← ebp+16, ...
低地址 → [空栈空间] ← esp指向这里
6. 性能影响分析
无参数调用:
- 栈操作:1次call(隐含1次push)
- 内存访问:无额外内存访问
- 指令数:1条call指令
有参数调用(N个参数):
- 栈操作:N次push + 1次call(隐含1次push)+ 1次add esp
- 内存访问:N次参数加载 + N次push写入
- 指令数:约3N+2条指令
7. 实际堆栈示例对比
场景1:无参数调用
c
调用前: ESP = 0x0018FF34
调用中: ESP = 0x0018FF30 (压入返回地址)
堆栈内容: [0x0018FF30: 0041246E] ← 返回地址
场景2:双参数调用
c
调用前: ESP = 0x0018FF34
push y: ESP = 0x0018FF30, [0x0018FF30: 30] ← 参数y
push x: ESP = 0x0018FF2C, [0x0018FF2C: 10] ← 参数x
call: ESP = 0x0018FF28, [0x0018FF28: 004124AF] ← 返回地址
清理后: ESP = 0x0018FF34 (恢复原状)
总结
从堆栈角度看,传递参数的本质是在调用函数前,在栈上为被调用函数准备数据空间。这种区别导致:
- 栈空间占用:有参数调用需要更多栈空间
- 执行开销:有参数调用有额外的push/pop操作
- 内存访问:有参数调用需要访问参数内存位置
- 代码复杂度:有参数调用需要维护参数传递协议
这就是为什么在性能敏感的场景中,应该尽量减少参数数量,或者使用寄存器传递参数(如x64架构的fastcall约定)。
4. 函数内部是如何使用这些传入的参数的呢
从你提供的汇编代码可以看出,在x86架构下使用__cdecl
调用约定时,函数内部访问参数的方式。让我详细分析每种场景:
1. 单个参数函数 funcOneParam(int a)
参数访问方式:
c
mov eax, dword ptr [a] ; 从栈中读取参数a的值到eax寄存器
mov dword ptr [result], eax ; 将eax的值存入局部变量result
栈帧结构:
c
[ebp+8] - 参数a
[ebp+4] - 返回地址
[ebp] - 保存的ebp
[ebp-4] - 局部变量result
2. 两个参数函数 funcTwoParams(int a, int b)
参数访问方式:
c
mov eax, dword ptr [a] ; 读取第一个参数a
add eax, dword ptr [b] ; 加上第二个参数b
mov dword ptr [result], eax ; 存储结果
栈帧结构:
c
[ebp+12] - 参数b
[ebp+8] - 参数a
[ebp+4] - 返回地址
[ebp] - 保存的ebp
[ebp-4] - 局部变量result
3. 三个参数函数 funcThreeParams(int a, int b, int c)
参数访问方式:
c
mov eax, dword ptr [a] ; 读取参数a
add eax, dword ptr [b] ; 加上参数b
add eax, dword ptr [c] ; 加上参数c
mov dword ptr [result], eax ; 存储结果
栈帧结构:
c
[ebp+16] - 参数c
[ebp+12] - 参数b
[ebp+8] - 参数a
[ebp+4] - 返回地址
[ebp] - 保存的ebp
[ebp-4] - 局部变量result
4. 多个参数函数 funcManyParams
(15个参数)
参数访问方式:
c
mov eax, dword ptr [a1] ; 读取a1
add eax, dword ptr [a2] ; 加上a2
add eax, dword ptr [a3] ; 加上a3
; ... 继续加上a4到a15
mov dword ptr [result], eax ; 存储最终结果
栈帧结构:
c
[ebp+64] - 参数a15 (最后一个参数)
...
[ebp+12] - 参数a2
[ebp+8] - 参数a1 (第一个参数)
[ebp+4] - 返回地址
[ebp] - 保存的ebp
[ebp-4] - 局部变量result
关键观察:
-
参数传递顺序 :在
__cdecl
调用约定下,参数从右向左压栈,所以最后一个参数在栈的最高地址。 -
参数访问方式 :所有参数都通过
[ebp+offset]
的方式从栈中访问,偏移量计算为:- 第一个参数:
[ebp+8]
- 第二个参数:
[ebp+12]
- 第n个参数:
[ebp+8+4*(n-1)]
- 第一个参数:
-
局部变量存储 :局部变量
result
存储在栈帧的[ebp-4]
位置。 -
栈帧建立:每个函数开始都执行标准序言:
cpush ebp mov ebp, esp sub esp, 0CCh ; 为局部变量分配空间
-
栈帧清理:函数结束时恢复栈指针:
c
mov esp, ebp
pop ebp
这种参数传递方式确保了函数能够正确访问调用者传递的所有参数,无论参数数量多少。