涉及到函数栈帧的创建和销毁的问题
局部变量的创建
为什么局部变量的值是随机的
函数是怎么传参的,传参顺序是怎样的
函数调用是怎么做的
函数调用结束后怎么返回
每一个函数调用,都要在栈区调用一块空间
那么什么是栈呢?
栈被定义为一种特殊的容器,我把它想象为一个盘子。
可以将数据压入栈中(push),也可以将已经压入栈中的数据弹出(pop)
但是栈这个容器必须遵守一条规则:先入栈的数据后出栈(First In Last Out, FIFO)
就像往盘子上一层一层地放松饼,最先放的在最底下,你只能先吃最上面那块。
举例用的代码
#include<stdio.h>
int Add(int x, int y) {
int z = x + y;
return z;
}
int main() {
int a = 5;
int b = 10;
int c = 0;
c = Add(a, b);
printf("%d", c);
return 0;
}
栈是从高地址向低地址延伸的。
但是这个栈帧是怎么开辟的呢?
这里就涉及到寄存器的知识,最开始只需要了解两个寄存器,多了记不住
esp(栈顶指针),ebp(栈底指针),他们是用来维护函数栈帧的
跟着教程调试一波就会发现main函数被另一个函数调用了,另一个函数又被另一个函数调用
无限套娃了纯属是
到这里我就很懵,但是其实这个没那么重要我觉得,所以不管他
总之main函数也是被调用的
首先来到调用main的函数的栈帧
esp减少四字节,因为栈是从高到低的,所以直观上来看就是往上移了一格
经过push操作,将ebp的值压入栈

再经过mov操作把esp的值赋给ebp,esp再往上移0E4h个字节

但是这还没完,还有好几个寄存器
ebx,esi,edi这三个值看上去就很难记,所以不用关心它们,只需要通通压上去

接着经过lea(load effctive address,加载有效地址)+mov+mov等一套组合拳
把一串数据赋给了从edi开始的为main函数开辟的空间
(这里我没有很懂)

这样main函数的栈帧就开辟好啦
这时候来到前三条赋值代码

如果没有赋值那么如果打印出来就会是随机值
(a和b应该间隔两个,但是我画不下了qwq)
然后又来到了Add函数,这里的重要问题是要搞清楚如何传参
这里并没有独立去开辟新的空间去接收形参
而是通过寄存器去找到我们之前在main函数里压栈进去的实参
所以说形参是实参的临时拷贝,改变形参是不会影响实参滴
并且参数是从右向左传的,Add(a,b)先传b再传a,这样找的时候也先找到a
拷贝好后就是基本重复开辟main函数栈帧的过程,
开辟Add函数的栈帧,在栈顶压入三个值并传值

然后找到事先拷贝好的形参,就是x和y

现在调用完了函数,该返回了
这里有一个比较容易钻牛角尖的点就是z也是要销毁的,它怎么能把值传回去捏

这里又要用到万能的寄存器了,用eax把值存起来
(eax是全局范围的,不用纠结具体作用)
接着就是销毁
销毁松饼也要从顶上开始,所以首先一连三个pop操作把edi,esi,ebx吃掉
再用mov把ebp的值赋给esp
再pop一个ebp(怎么创建就倒过来销毁嘛)
这个时候ebp和esp都返会到main函数里面去了
(这个点也不清楚,难点)
接着是一个ret指令,之前存的call指令的下一条指令的地址就用上了
之后的过程非常复杂,很多文章和视频都跳过了
反正就是返回值先给寄存器,回到主函数后再由寄存器赋给变量