C函数栈帧开辟以及回退过程

全文基于_cdecl函数调用约定

#include <stdio.h>

int sum(int a, int b) {
	int temp = 0;
	temp = a + b;
	return temp;
}

int main() {
	int a = 10;
	int b = 20;

	int ret = 0;
	ret = sum(a, b);
	printf("ret = %d\n", ret);
	return 0;
}

我们编译代码查看汇编指令,所有函数第一条语句前面都会有这么一段代码,我们在讲sum函数的时候讲
在这里插入图片描述

栈上局部变量都是通过栈底指针ebp偏移访问,不生成符号,不属于数据,属于指令

在这里插入图片描述
根据上面三条汇编指令,我们可以画出如下栈空间示意图,ebp是main函数的栈底指针,保存的是一个地址
在这里插入图片描述
接下来就是调用sum函数,首先需要实参压栈,C/C++是从右向左压栈,而有些语言,比如PASCAL是从左向右压实参的

由于C/C++是需要支持可变长参数,实参的个数不固定,形参用...来接收实参,所以是从右向左压栈,这样压栈的时候就能知道用户到底传入了多少个实参,而从左向右压栈无法确定用户传入了多少个实参

入参顺序可参考:C函数调用约定和返回值

在这里插入图片描述
将实参压栈后,我们可以得到以下栈空间图
在这里插入图片描述

形参空间是在调用函数栈帧(main)中开辟的,而不是在被调用函数栈帧(sum)中开辟,每压栈一个实参,都会开辟一个形参的空间,栈顶指针esp都会减4字节(栈顶指针esp为低地址,栈底指针ebp为高地址)

实参压栈完成后,需要调用call指令,去执行sum函数,执行完sum函数后需要回到调用指令(call)的下一条指令继续执行

所以call指令需要做以下两件事:

  • 把call指令下一行指令的地址(0x0040109A)入栈
  • jmp跳转

在这里插入图片描述

执行call指令后,程序跳转到了这么一个地方,而不是sum函数的指令部分

在这里插入图片描述
符号重定向:编译阶段是不分配符号地址的,因为我们当前文件可能引用外部的符号,而编译阶段是独立编译的,我们链接的时候才会进行符号解析、合并符号表等操作,之后再给符号分配内存地址

也就是说,编译阶段给符号分配的地址都是不合法的,对于数据符号编译阶段分配的是0地址(绝对虚拟地址),函数符号则分配的是-4(相对虚拟地址)。链接后给函数符号分配的地址是与下一行指令地址的一个偏移量,这样当程序需要跳转到某个函数地址的时候,取出PC寄存器保存的地址(下一行指令的地址)与该偏移量相加就得到函数的入口地址

进入sum函数后,需要执行这么一段指令,计算真正的sum函数地址,才执行sum函数代码

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
总结,每次执行一个函数之前都需要执行三个操作:

  • 调用函数栈顶main成为被调用函数sum栈底(mov ebp,esp)
  • 移动栈顶指针esp,给被调用函数sum开辟栈帧(sub esp,44h)
  • 初始化新栈帧内存(rep stos dword ptr [edi])

接下来需要执行以下指令
在这里插入图片描述

在这里插入图片描述

局部变量通过栈底指针ebp负向偏移访问,形参通过ebp正向偏移访问,eax为a+b的计算结果,讲计算结果赋值给temp,return的时候将temp的值赋值给eax,给调用方返回

栈帧清退
在这里插入图片描述
栈帧清退的时候修改esp和ebp,用ebp给esp赋值,从栈上取出调用方栈底地址给ebp赋值
在这里插入图片描述
可以看到,开辟栈帧的时候我们对占内存进行了初始化,但是栈帧清退的时候仅仅就是修改了esp和ebp,没有做其他任何操作,如果我们此时通过一些手段去访问已被清退栈帧的内存,还是可以访问到的,因为数据还存在

现在执行ret指令,相当于pop IP,把栈顶元素的值(调用处下一条指令的地址)赋给PC寄存器,并且移动esp

(IP寄存器和PC寄存器是一个东西)
在这里插入图片描述
sum函数执行完成后,取出PC寄存器中存放的地址继续执行,这是回到call指令的下一条指令处,这条指令的操作就是回收形参变量内存,形参内存由调用方开辟和释放

而sum函数的返回值由eax寄存器带回来
在这里插入图片描述
函数调用过程中用pop ebp恢复栈底,用ret指令取出在栈上保存的返回地址存到PC寄存器

sum函数栈底保存的是main的栈底地址,main函数栈底保存的是调用main的函数的栈底地址

mov:对于变量,加不加[]都表示取值;对于寄存器而言,无[]表示取值,有[]表示取地址
lea:对于变量,其后面的有无[]皆可,都表示取变量地址,相当于指针。对于寄存器而言,无[]表示取地址,有[]表示取值。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bugcoder-9905

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值