函数栈帧的创建和销毁

  • 局部变量是怎么创建的?
  • 为什么局部变量的值是随机值?
  • 函数是怎么传参的?传参的顺序是怎么样的?
  • 形参和实参是什么关系?
  • 函数调用是怎么做的?
  • 函数调用结束后是怎么返回的?

这些和函数栈帧的创建和销毁有关,下面会慢慢解答:

对于函数栈帧的创建和销毁,编译器越高级,越不容易学习和观察。

函数栈帧的创建和销毁的过程在不同的编译器上是略有差异的,大体逻辑是一致的,具体细节取决于编译器的实现。
函数栈帧的创建和销毁_第1张图片

正在调用哪个函数,ebp和esp维护的就是哪个函数的函数栈帧
函数栈帧的创建和销毁_第2张图片
函数栈帧的创建和销毁_第3张图片
函数栈帧的创建和销毁_第4张图片
在main函数调用的过程中:

(压栈push是给栈顶放一个元素,出栈pop是从栈顶删除一个元素)

push是压入元素
push完esp就会变化
然后esp的指向就会变成下图

函数栈帧的创建和销毁_第5张图片

move是把esp的值给ebp
函数栈帧的创建和销毁_第6张图片
sub是减,给esp减去0E4h
esp变小,也就是esp指向了上面的区域
函数栈帧的创建和销毁_第7张图片
push了 ebx esi edi之后
函数栈帧的创建和销毁_第8张图片
lea加载有效地址,相当于给edi里加了个地址
函数栈帧的创建和销毁_第9张图片
把39h放到ecx,eax里面放的是0CCCCCCCCh这样的值

然后下面的那句话的意思是:要把从edi这个位置开始向下的39h个dword(double word)(一个word就是2个字节,dword就是4字节)的数据全部改成eax的内容

每次操作4个字节,操作39h次,然后改成eax的内容
函数栈帧的创建和销毁_第10张图片
上面main函数的栈帧已经开辟好,然后准备执行有效代码
函数栈帧的创建和销毁_第11张图片
把0Ah这个十六进制数字(0A也就是十进制10)放到ebp-8的位置
函数栈帧的创建和销毁_第12张图片
函数栈帧的创建和销毁_第13张图片

如果没有对a初始化,那么默认就是那些CCCC…
之前我们经常能打印出随机值烫烫烫烫,就是因为内存中放的是CCCC…这样的值,不同编译器上放的随机值不一样,所以变量最好进行初始化

在这里插入图片描述
把14h这个十六进制数字放到ebp-14的位置
函数栈帧的创建和销毁_第14张图片
空了两个整形的位置函数栈帧的创建和销毁_第15张图片
别的编译器可能紧挨着放,因为这个空出的大小取决于编译器
在这里插入图片描述

把0放到ebp-20h的位置
函数栈帧的创建和销毁_第16张图片
在函数中局部变量是怎么创建的?
函数栈帧的创建和销毁_第17张图片
首先为我这次的函数调用创建函数栈帧,然后在它的函数栈帧里找到空间把a,b,c什么的放进去。

函数栈帧的创建和销毁_第18张图片
当函数调用时
把ebp-14h里的值放到eax里,就是把20放到eax里面
在这里插入图片描述
然后push eax
函数栈帧的创建和销毁_第19张图片
函数栈帧的创建和销毁_第20张图片
把ebp-8里的值放到eax里,就是把 a(10)放到ecx里面
然后push ecx,把10压到顶上函数栈帧的创建和销毁_第21张图片
函数栈帧的创建和销毁_第22张图片
call是调用函数,先记住call的地址函数栈帧的创建和销毁_第23张图片
调用之后:
函数栈帧的创建和销毁_第24张图片
00 c2 14 50

call这个动作把call的下一条指令的地址压到上面了
函数栈帧的创建和销毁_第25张图片

记住这个地址的原因是,call之后去调用add函数,跳到add函数里之后,调用完add函数之后还需要返回来,要回哪去?回到call指令的下一条指令的位置,然后再从这个地址往下执行

进入函数之后:
函数栈帧的创建和销毁_第26张图片
和main函数开辟函数栈帧的过程一样,是为add函数准备函数栈帧

push ebp之后:
函数栈帧的创建和销毁_第27张图片
函数栈帧的创建和销毁_第28张图片
move 把esp的值给ebp
函数栈帧的创建和销毁_第29张图片
然后sub esp减去0CCh,esp就指向了上面的空间
函数栈帧的创建和销毁_第30张图片
然后push 三个数:
函数栈帧的创建和销毁_第31张图片
然后往下就是像main函数一样把空间中的内存初始化为CCCCCCCC
函数栈帧的创建和销毁_第32张图片
准备执行计算
函数栈帧的创建和销毁_第33张图片
z的创建:把0放到ebp-8的位置
函数栈帧的创建和销毁_第34张图片
ebp+8找到ecx中的10放到eax
然后把eax中的20加到eax
函数栈帧的创建和销毁_第35张图片
eax中的值此时就为30了
然后把eax中的值放到ebp-8的位置,也就是z的位置

函数在调用计算的时候,形参不是主动创建的,是因为在调用函数的时候就把参数传过去了,参数从右向左传的在这里插入图片描述
形参根本不是在add函数内部创建的,而是回来找了在调用的时候传参压的那块空间函数栈帧的创建和销毁_第36张图片
有这样一句话:形参是实参的临时拷贝,在这里也得到验证

这时候到返回部分了:
函数栈帧的创建和销毁_第37张图片
把[ebp-8]的值放到eax里(也就是把z的值放到eax里),eax是一个寄存器,不会因为程序退出就销毁。因为z出函数后销毁后值就不在了,所以暂时把z的值放到寄存器里,等回到主函数再把eax的值拿出来用就可以了。

三次pop之后:
esp指向的位置就发生了改变
函数栈帧的创建和销毁_第38张图片
回收空间操作:把ebp赋给esp
esp就指向下面去了
函数栈帧的创建和销毁_第39张图片
然后pop ebp把栈顶元素弹出
栈顶元素存的是main函数的ebp也就是下图位置的地址
函数栈帧的创建和销毁_第40张图片
main-ebp存在栈顶的原因是:在函数调用返回之后随着函数栈帧的销毁,main函数的栈顶容易找到,但是main函数的栈顶就不记得了,所以先把main函数的栈顶存在main-ebp。
pop弹出把main-ebp的值弹到ebp,ebp就重新指向了main函数栈底的位置,同时esp往下,回到了main函数函数栈帧的创建和销毁_第41张图片

main函数这块空间又由ebp和esp维护了
从main函数回来之后,我们应该从call指令的下一条指令的地址往下执行
函数栈帧的创建和销毁_第42张图片
ret指令的作用就是:return返回的时候,这个指令把esp指向的那个call指令的下一条指令的地址弹出并且跳到那个位置函数栈帧的创建和销毁_第43张图片
在这里插入图片描述

esp+8之后就把x,y形参的空间释放了
函数栈帧的创建和销毁_第44张图片
在这里插入图片描述
把eax寄存器中的值放到ebp-20h,也就是放到c中
c=Add(a,b)返回值这时候带回来了

你可能感兴趣的:(c语言初阶学习笔记,c语言,学习)