=========================================================================
相关代码gitee自取:C语言学习日记: 加油努力 (gitee.com)
=========================================================================
接上期:
学C的第十天(继续深入学习函数、函数递归、练习)-CSDN博客
=========================================================================
函数栈帧的创建和销毁
- 越高级的编译器,越不容易学习和观察该过程
- 同时在不同的编译器下,函数调用过程中栈帧的创建是略有差异的,
具体细节取决于编译器的实现
寄存器:ebp 和 esp(和函数栈帧有关)
esp:栈顶指针;ebp:栈低指针
寄存器集成在CPU上的
- ebp 和 esp 这两个寄存器中存放的是地址
- 这两个地址是用来维护函数栈帧的
1. 每一次函数调用,都要在栈区创建一个空间
2. 正在调用哪个函数,esp 和 ebp 就在维护哪个函数的函数栈帧
3. esp 和 ebp 之间的空间就是系统为这次函数所调用的空间,叫这次函数的函数栈帧
4. 栈区的使用习惯是先使用高地址,再使用低地址
5. 空间消耗时,从高地址向低地址消耗
6. 再开辟新空间时,使用的空间是上面的空间(往上使用)
7. 像栈一样,放数据是在顶上(栈顶)放数据
测试代码:
#include 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;}
函数栈帧图示:
在VS2013中,main函数也是被其它函数调用的
mainCRTStartup–>__tmainCRTStartup–>main函数
(调用)(调用)
实际开辟的空间为:
(查看汇编代码:)
函数栈帧实现过程(重点):
(1).push(压栈):给栈顶放一个元素
[ 补充:pop(出栈) –> 从栈顶删除一个元素]
(压栈前:)
(压栈后:esp会往上移,移到压的元素上方)
———————————————————————————————
(2).mov(把后面的值赋给前面,把esp的值赋给ebp):
———————————————————————————————
(3).sub(让esp减去一个十六进制数):
———————————————————————————————
(4).连续push三次:
———————————————————————————————
(5).lea(load effective address — 加载有效地址,
把一个有效地址加载到edi中):
———————————————————————————————
(6).两次mov后,rep stos:
之前出现过的“烫烫烫”乱码的原因:
变量未初始化,变量里面的数据就是“cc cc cc cc”,
这些“cc cc cc cc”在使用后会产生随机值,
即“烫烫烫”,而初始化就会将这些随机值覆盖。
———————————————————————————————
(7).产生局部变量:int a = 10; (mov)
———————————————————————————————
(8).产生局部变量:int b= 20; (mov)
———————————————————————————————
(9).产生局部变量:int c= 0; (mov)
=====================================================================
(总结上面步骤)局部变量(上面的a、b、c)的创建过程:
- 为这次函数调用创建函数栈帧 — (1)~(6)
- 在函数栈帧中找到空间把局部变量放进去 —(7)~(9)
=====================================================================
(10).调用函数:传参(mov)
———————————————————————————————
(11).调用函数:传参(push)
———————————————————————————————
(12).调用函数:传参(mov)
———————————————————————————————
(13).调用函数:传参(push)
———————————————————————————————
(14).call:调用函数(进入Add()函数)
———————————————————————————————
(15).进入Add()函数后:
(当前开辟的空间情况:)
———————————————————————————————
(16).Add()函数 的 push:
———————————————————————————————
(17).Add()函数 的 mov:
———————————————————————————————
(18).Add()函数 的 sub:
———————————————————————————————
(19).Add()函数 的 连续三次push:
———————————————————————————————
(20).Add()函数 的 lea(加载有效地址) –> mov –> mov –> rep stos:
———————————————————————————————
(21).Add()函数中产生局部变量:int z= 0; (mov)
———————————————————————————————
(22).Add()函数中进行计算:z = x + y;
形参的产生和使用:
- 形参是对实参的临时拷贝:形参是调用的main函数中对变量的拷贝,
即下图ecx 和 eax所以改变形参,改变的也只是 ecx 和 eax,
并不会改变main函数中的实参- 压栈时:先压的b’,所以在a‘下面,所以传参是先传的形参y,再传的形参x
- 形参的使用:通过指针的偏移量找到形参
———————————————————————————————
(23).Add()函数计算后进行返回:return z;
———————————————————————————————
(24).Add()函数调用完后销毁空间返回main函数:
pop — 出栈(弹出栈顶元素)
———————————————————————————————
(25).Add()函数调用完后销毁空间返回main函数:ret — call函数调用完后,
返回main函数call的下一条指令(之前留的地址会出栈)
———————————————————————————————
(26).main函数:销毁形参
———————————————————————————————
(27).main函数:使用Add函数的返回值
———————————————————————————————
(28).最后main函数的销毁是和Add()函数的销毁是类似的