C语言中函数栈帧的创建

本文深入探讨了C语言中函数调用时栈帧的创建和销毁过程,通过实例代码分析了从函数参数传递、局部变量创建到函数返回的内存操作。涉及esp、ebp寄存器的作用,以及栈区的使用规则。通过反汇编展示了在Visual Studio 2013环境下函数栈帧的具体变化,解答了关于局部变量、形参与实参、函数返回等问题。
摘要由CSDN通过智能技术生成

学习C语言的过程中,我们经常会进行函数调用,我们对于函数调用表面现象倒是一清二楚,但是对于其具体在内存中是如何让运作,却不怎么了解,今天我们就来好好探究里面的奥妙。

想要了解函数栈帧的创建和销毁,首先必须了解以下知识点:

知识介绍:

我们电脑CPU中会有一个小型存储区域,用来暂时存放参与运算的数据和运算的结果,那就是寄存器。寄存器具有高速度,低空间的特点。寄存器有eax、ebx、ecx、edx、esi、edi、eip、esp、ebp,我们这里着重介绍esp、ebp。

esp(栈顶指针)、ebp(栈底指针)这两个寄存器中存放的是地址,而这两个地址是用来维护函数栈帧的,具体意思就是esp、和ebp之间的空间就是栈帧。

内存中有一个分区,名为栈区,函数的每一次调用,都需要在栈区上开辟空间,而这些开辟的空间就可以当作函数栈帧,而且函数栈帧随着函数的执行一般会有所变化。

栈区的中的函数和数据等是从高地址向低地址创建的,压栈就是把数据放入栈顶,出栈就是把数据取出栈顶,所以会有先入后出的特点。

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

int main()
{
	int a = 10;
	int b = 20;
	int sum = 0;
	sum = add(a, b);
	return 0;
}

我们在windows10的visual studio 2013编译器(debug、32位,以下简称vs2013)环境下进行测试。为展示效果,代码(如上图)就写的尽可能详细。

在vs2013中通过对main函数定义的查看可以了解,main函数是被一个名叫mainCRTStartup的函数调用,而mainCRTStartup又被一个名叫__tmainCRTStartup的函数调用,因此main函数也会在栈区上开辟属于自己的空间。

用一幅图来总结上面的知识。

 演示函数栈帧的创建和销毁:

我们对代码进行反汇编可以看见函数栈帧创建和销毁的具体过程(如下图所示)。接下来我就把整个过程详细的讲解一番。(反汇编中的地址和栈区中的地址不一样)

1.为main()函数开辟栈帧

 

push压栈,esp的值减少4个字节,然后将ebp的值压入栈中。

move移动,将esp的值赋给ebp,这里并不是将esp所指向的空间赋给ebp,只是单纯的地址赋值。

sub减去,将esp的值减去0E4h字节保存在esp中,即esp向低地址移动0E4h字节。 

将esp的值减少4字节,然后将ebx的值压入栈中。

将esp的值减少4字节,然后将esi的值压入栈中。

将esp的值减少4字节,然后将edi的值压入栈中。

lea将一个内存地址的值直接赋给操作数,rep能够引发其后字符串指令被重复,stos将eax的值中的值拷贝到es:[edi]指向的空间,dword双字(四个字节),ptr指针,es:[edi]指向目的串。

在栈上从edi-0E4h开始的位置,向高地址方向内存赋值0CCCCCCCCh,重复39次,每次赋值双字(4个字节)的空间。

2.main()函数创建局部变量

将0Ah赋给a所在的内存地址指向的空间

将14h赋给b所在内存地址所指向的空间

将0赋给sum所在内存地址所指向的空间

 

3.形参的传递

 

esp的值减少4个字节,将14h的值压入栈中(b的形参,即y)

esp的值减少4个字节,将0Ah的值压入栈中(a的形参,即x)

可以看出,虽然我们写的是int add(int x, int y),但是传参顺序确实从右往左,即先创建y,再创建x

,也知道形参和实参是两块不同空间储存,改变形参不会改变实参,即形参是实参的临时拷贝。

call将下一条指令的地址压入栈中(当子程序执行完之后很根据这个地址返回),并移动到调用的子程序中(跳转到后面的地址)

 

jump将无条件跳转到后面的地址。

 4.为add()函数开辟栈帧

 这里和mian()函数的函数栈帧开辟如出一辙。总的概括就是:

esp减少4字节,将ebp的值压入栈中

将esp的值赋给ebp,这里并不是将esp所指向空间的值赋给ebp

将esp的值减去0CCh字节保存在esp中,即esp向低地址移动0CCh字节。

将esp的值减少4字节,然后将ebx的值压入栈中。

将esp的值减少4字节,然后将esi的值压入栈中。

将esp的值减少4字节,然后将edi的值压入栈中。

在栈上从edi-0CCh开始的位置,向高地址方向内存赋值0CCCCCCCCh,重复33次,每次赋值双字(4个字节)的空间。

 

将0赋值给z所指向的双字节空间

将x所指向的双字节空间的值赋给eax

add是加法运算,将y所指向的双字节空间的值加给eax

将eax的的值赋给z所指向的双字节空间

将z所指向双字节空间的值赋给eax

 5.为add()函数销毁栈帧

取出edi在栈中的值,esp的值增加4字节

取出esi在栈中的值,esp的值增加4字节

取出ebx在栈中的值,esp的值增加4字节

将ebp的值赋给esp,这里并不是将ebp所指向的内存空间的值赋给esp

取出ebp在栈中的值,esp的值增加4字节

ret会取出栈中存放的call下一条指令的地址,用于返回main()函数,esp的值增加4字节

 

给esp的值加上8个字节

将eax的值赋给sum所指向的双字节空间

 6.为main()函数销毁栈帧

可以看出,之后就是main()函数的栈帧销毁了,这里就不详细展开讲了,留给有兴趣的老铁研究研究。

总结:

以上就是函数栈帧创建和销毁的大概流程了,看到这里,想必以前学习中的许多困惑已经有了答案了吧。

比如:

1.局部变量是怎么创建的?

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

3.函数是怎么传参的?

4.传参的顺序是怎么样的?

5.形参和实参什么关系?

6.函数调用以后怎么返回的?

……

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值