函数栈帧的创建和销毁(底层)

本文详细解析了C语言中函数栈帧的创建、销毁过程,包括局部变量的创建、初始化、函数传参顺序、形参与实参的关系,以及函数调用和返回的底层逻辑。通过实例分析,展示了VS2013中函数栈帧的变化,揭示了编译器如何维护栈帧空间,并探讨了函数调用链及返回值的处理方式,帮助读者深入理解函数调用的底层机制。
摘要由CSDN通过智能技术生成

  局部变量是怎么创建的?

  为什么局部变量的值是随机值?

  函数是怎么传参的?传参的顺序是怎么样的?

  形参和实参是什么关系?

  函数调用是怎么做的?

  函数调用是结束后怎么返回的?

  这些问题今天我们都会通过函数栈帧的创建和销毁一一和大家讨论到,不过这些知识点比较底层,我个人来说只能用比较通俗的讲法来与大家讨论,望谅解。

  我们今天就不用VS2019来演示这个函数栈帧的创建和销毁的过程了,因为编译器越高级,这个函数栈帧的过程分装得越不利于我们观察,所以今天我们会选择VS2013来结合讨论这个函数栈帧的问题。这里再和大家提一点,就是函数栈帧的创建和销毁的过程在不同的编译器底下,它的创建和销毁是略有差异的,但是它的大体逻辑是一致的,具体细节取决于编译器,但我们今天要探讨的就是这个大体的逻辑,所以大家不用担心。

  在讲函数栈帧之前,要先做一些铺垫。

  第一,我们知道计算机里面有寄存器,寄存器有eax、ebx、ecx、edx,这几个寄存器我们平时都会遇到,今天再重点说到另外两个寄存器,一个叫ebp,另一个叫esp

  要理解清楚函数栈帧,就必须理解这两个寄存器。esp和ebp这两个寄存器中存放的是地址,这两个地址是用来维护函数栈帧的。这里我们结合一段最简单的调用函数代码来讲一讲这个知识点。笼统的讲就是,每一个函数的调用,都要在栈区上创建一个空间,

#include<stdio.h>

int Add(int x, int y)
{
	int z = 0;
	z = x + y;
	return z;
}


int main()
{
	int a = 10;
	int b = 20;
	int c = 0;
	c = Add(a,b);

	printf("%d\n", c);

	return 0;
}

  这样一段足够简单的代码,每一个函数调用都要为它开辟空间,假设现在Add函数被main函数调用,然后这里结合画图讲:

  假设这个大绿色框是我们的栈区内存,那么被选中的图中的下面这一部分就是为当前main函数开辟的一个空间,下面是内存的高地址,上面是内存的低地址,而且这一段空间就叫做main函数的函数栈帧,这个空间就由刚才我们说到的两个寄存器来维护,ebp寄存器当前就存的是如图下方的这个地址,esp就是上方的这个地址。注意,esp与ebp中间维护的空间是当前调用的函数对应的函数栈帧,笼统的讲就是,在调用哪个函数,esp与ebp就去维护哪个函数的函数栈帧。通常我们把ebp称为栈底指针,esp称为栈顶指针。

  讲到这,大家还要了解到另外一个点,我们打开调试窗口里面的调用堆栈,如下图操作:

   然后我们按F10让程序往下走,当走到main函数里面时,我们可以从右方的函数调用堆栈里面看到main函数给调用起来了,这里就产生了一个问题了?main函数到底给谁调用了呢,这个问题从这里看不到,这个时候再让代码往下走,我们可以从右方看到__tmainCRTStartup(),左侧就可以看到这个函数调用了main函数。

   那么现在我们知道了main函数也是被调用的,被__tmainCRTStartup()调用,那么__tmainCRTStartup()又是被谁调用的呢,从右侧就可以看到它是被mainCRTStartup()所调用的,如下图所示:

     其实这里我们就可以从左侧观察到__tmainCRTStartup()调用了main函数,main函数我们知道每次我们写到return0,但是我们不知道main函数调用完后返回值去哪里了,其实就是返回到mainret里面去了,如下图所示:

   这就是main函数这么一个调用逻辑这个点,这里有个结论就是:在VS2013中,main函数也是被调用的,被__tmainCRTStartup()调用,__tmainCRTStartup()又是被mainCRTStartup()所调用的。

   为什么要花那么多时间来插入这个点呢,就是因为栈区上使用内存空间的时候,每一次函数调用都要在栈区上分配对应的空间,所以在调用main函数之前esp和ebp应该跑去维护过__tmainCRTStartup()和mainCRTStartup()函数了,调用main函数之后,esp和ebp也会相应的跑去维护Add函数,用画图表示:

  大概就是这么一个分配空间的思路,接下来就具体的来研究一下这个分配的过程。

  现在按下F10,然后转到反汇编,操作如下:

   然后现在就可以看到C语言所对应的汇编代码了,我们这里选择取消符号名,方便我们待会观察地址,如下图:

   第一行我们就可以看到push ebp,我们刚才知道,在调用main函数之前,调用了__tmainCRTStartup()函数,既然这一步汇编代码已经走入了main里面,那么在走入main函数之前,__tmainCRTStartup()的函数栈帧已经创建好了,这一块函数栈帧空间就应该给ebp和esp维护过,画图就是:(注意接下来讲的步骤都是从下往上走,对应的就是高地址往低地址走)

  ebp存的就是__tmainCRTStartup的栈底的地址,现在第一行汇编代码说的是push ebp,push的意思就是压栈,现在就要把ebp这个元素压在栈的顶上,然后此时esp也要移动到相对应的栈顶,继续维护。

  这里的压栈步骤也可以通过监视窗口观察到这个动作。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值