C++函数调用流程分析

本文详细分析了C++函数调用的栈空间、函数调用栈和调用流程。通过实例展示了从栈帧创建、参数压栈、执行函数到返回值处理的完整过程,解释了为何要保存相关信息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


栈空间

程序的虚拟内存空间可以分为内核空间和用户空间,栈空间就是从用户空间的最高内存地址开始向下增长的空间。栈空间在程序运行期间,主要作用就是维护函数调用的上下文,栈的数据结构(后进先出)也与函数调用流程相符。


函数调用栈

函数调用栈,是将每个函数所用的信息,称之为活动记录或者栈帧,按照调用的顺序依次压入栈中(保存在栈空间中),等最上层的函数执行完了,就弹出相应的栈帧,栈帧主要包括以下几个内容:

  • 函数的参数和返回地址
  • 旧的EBP
  • 保护寄存器的值
  • 局部变量
  • 其它数据

    下面详细地解释函数调用的整体流程,以及为什么要保存这些信息

函数调用流程

我们以一个简单的C++程序来实验下函数调用的整体流程

#include <iostream>
using namespace std;


int Add(int a, int b)
{
	int c = 0;
	c = a + b;
	return 0;
}


int main()
{
	int a = 10;
	int b = 20;
	int ret = 0;
	ret = Add(a, b);
	cout << ret << endl;
}

反汇编(visual studio2022 windows debug x-86)如下图所示

  1. push ebp 将EBP里的值入栈

  2. move ebp,esp 将栈顶指针的地址传给EBP

  3. sub esp,0E4h ESP减去0E4h,相当于把栈顶指针向下移,预留栈空间

  4. push ebx

  5. push esi

  6. push edi 到这就做好了寄存器的保护

  7. lea edi,[ebp-24h] mov ecx,9 mov eax,0CCCCCCCCh rep stos dword ptr es:[edi],这几个指令表示从 EBP-24h 到 EBP 的栈空间的值设置为 0xCCCCCCCCh

  8. mov dword ptr [a],0Ah mov dword ptr [b],14h mov dword ptr [ret],0 将局部变量赋值,可以查看到局部变量所在的地址

    EBP 的值为 0x0137fb84,之前将 0x0137fb84 ~ 0x0137fb60 之间的值设为 0xCCCCCCCCh,现在将中间的 a,b,ret 局部变量设为对应值,图示为连续的地址,其实不是连续的,从上面的地址能看出来

    下面就要在main函数中调用一个函数 Add

  9. mov eax,dword ptr [b] push eax mov ecx,dword ptr [a] push ecx 将参数压栈(这里是从右向左压栈)

  10. call Add 调用Add函数,分为两步,一个是将EIP压栈,一个是跳转到对应函数地址开始执行(将EIP的值改为跳转命令的地址,然后执行跳转命令,EIP的值就改为了Add函数的开始地址),Add函数反汇编如下图所示

  11. 下面这部分和之前main函数入栈一样,将保护寄存的值入栈,并将栈区部分初始化为 0CCCCCCCCh,这里只有局部变量 c

  12. mov dword ptr [c],0 mov eax,dword ptr [a] add eax,dword ptr [b] mov dword ptr [c],eax 将 c 赋值为0,再赋值为 a+b,可以看下a,b,c的地址,可以看到这里的a,b就是我们在main中压栈的a,b,当时esp为0x0137fa94,压栈b->0x0137fa90,压栈a->0x0137fa8c

  13. 后面就是把eax清零(因为这里是返回0,如果返回的是c,会把eax的值改为c的值,这个在main函数的反汇编中可以看到 将eax的值赋给ret),在把保护寄存器的值出栈

  14. add esp,0CCh mov esp,ebp 将ESP改为EBP的值,相当于将中间的局部变量等出栈

  15. pop ebp 把旧的EBP的值弹出给EBP寄存器,而旧的EBP的值就是在main函数中EBP寄存器的值,此时EBP也回到main函数的状态

  16. ret 就是将返回地址弹出到EIP中,这样下面会继续执行main函数的后续指令,接续到main

  17. 回到main函数继续执行,add esp 8 就是把之前压栈的参数 a 和 b 出栈了,此时栈区回到调用 Add 函数前的状态,然后 mov dword ptr [ret],eax 如上面所说,将返回的值保存在eax,然后赋给ret

  18. 可以看到 main 函数返回的时候也执行了类似的操作,这样也就回到了main函数调用前栈区的状态

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值