👌写在前面的话👌:
🐵在初学c语言的时候大家可能会有一些困惑:
1:局部变量是如何创建的❓
2:为什么局部变量不初始化时值是随机值❓
3:函数是怎么传参的❓传参的顺序是怎样的❓
4:形参和实参的顺序是怎样的❓
5:函数调用是如何进行的❓
6: 函数调用结束后是如何返回的❓
如果让问题累积对以后深度学习c语言会不利,所以本博主查阅资料摸清原理后来分享给大家,希望大家能0️⃣困惑的学习。
如果大家觉得有帮助别忘了一键三连 。
点赞👍 收藏✨ 关注✌
目录:
一:栈区的使用规则
💡:图1
在这里我们必须要知道,每一次调用函数的时候都需要在栈区为被调用的函数开辟内存空间,该空间被称为-----被调函数的函数栈帧。 且栈区的使用规则是先使用高地址再使用低地址。
二:寄存器
🔑:
相信大家在图1中都看到了 esp 和 ebp ,这其实是两个寄存器,ebp内储存的是函数栈帧 栈低 的地址,而esp储存的时函数栈帧 栈顶 的地址,我们可以把这两个寄存器看成两个指针,它们之间的区域就是该函数的栈帧空间,它们共同维护函数的栈帧。
除了这两个本文要深刻理解的寄存器,计算机中其它的寄存器有:图 2
三:通过对一个简单程序的汇编代码进行分析
💡:通过对汇编代码进行分析是了解函数栈帧创建与销毁的高效方式。
🔑:总代码
#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;
}
1:main函数的栈帧创建过程
🔑1️⃣我们首先要知道main函数也是被其他函数所调用的
现在我用图解的方式带大家了解:图 3
🔑从图中大家可以看到main函数的函数栈帧创建的过程,大致可以分为以下几个步骤:
1️⃣:将寄存器ebp进行压栈,移动到原函数的栈顶。
这一步骤的意义是:如图 3 压栈上来的ebp寄存器中储存的是原函数栈帧的栈底地址(红色ebp所指)。
这样在出栈时能够之间通过该寄存器访问到原栈帧的栈底。通俗的来说在进栈时记得来时的路,便于出栈。
2️⃣:将红色的ebp移动到原函数栈帧的栈顶,即蓝色ebp所在位置。然后让原函数栈帧栈顶的指针即红色esp移动到蓝色esp。
3️⃣:将寄存器edi,esi,ebi进行压栈。
4️⃣:对新开辟的函数栈帧进行初始化。
图 3 是 vs2013 的结果,但由于不同编译器下,对函数栈帧的创建与销毁过程略有差异,但大体思路是一致的。
💡:到这里我们就可以解释在开头提出的问题2:为什么局部变量不初始化时值是随机的?
通过图 3 可以知道在函数栈帧创建的时候会在栈帧里放初始值,而不同编译器放的值是不同的,这也就导致了局部变量不初始化时,值是随机的。
2:在main函数中局部变量的创建
在main函数的栈帧创建完成后,会在栈帧空间中由高地址向低地址开始创建局部变量。
接下来用图说明局部变量是如何创建的。
3:函数的调用
💡:每一次函数调用都会在栈区开辟一块空间。
而函数调用时是先传参再为调用的函数创建函数栈帧。
🔑:接下来用图解释函数调用的过程 图 4
图 5
![]()
4:函数栈帧的销毁
🔑:由上文可知函数调用结束后,函数栈帧会销毁,但为什么 z=30 却能够把30带回主调函数?
想要弄清楚其中的缘由,就必须了解函数栈帧是如何销毁的。
接下来用图来解释函数栈帧销毁的过程:
四:总结
💡函数栈帧的创建和销毁是非常具有逻辑性的,在调用函数时,会先记录下现在语句所在的地址,然后进行被调用函数栈帧的创建,在层层调用结束后,程序会执行出栈,而刚好来时的路径都被记录下来,通过一个个地址就能准确返回到上一个函数调用自己的地方,再继续执行下去。
通过以上对汇编代码的分析,我相信大家经过理解后能够轻松的解答文章开头提出的问题。
本人是初学者,在对一些概念上的讲述上追求容易理解,因此有一些地方不严谨,还望大家理解!
如有错误,还望斧正。如有帮助不忘三连支持,万分感谢。(*^_^*)。