首先,在学习这个问题的时候,不要用太高级的编译器,越高级,越不容易学习和观察(封装得太复杂了)。同时在不同的编译器下,函数调用过程中栈帧的创建是略有差异的,具体细节取决于编译器的实现,但逻辑是一样的。
#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",c);
return 0;
}
栈区先使用高地址再使用低地址
创建这样一块代码的时候,每一个函数调用都要开辟一块空间
函数栈帧主要由这两个寄存器来维护。
调哪个空间ebp和esp就会在哪个空间,在调用哪个函数,正在调用哪个函数,ebp和esp就是维护哪块空间的函数栈帧,当这段代码运行到Add时就会直接去维护Add的函数栈帧。
所以他们两个的空间,就是为本次函数调用所分配的空间。
可以看到在这个地方main函数被调了
向上翻可以看到这个调用函数的函数名,在这个函数内部调用了main函数
这个函数又被
这个调用了,main函数的返回值就返回到了mainret里
所以在VS2013中main函数也是被其他函数所调用的。
再看是怎么调用到,到这里后右击鼠标到反汇编
看他的汇编代码
没有push的时候地址是这个
push完之后esp就会指上去,地址变小
再来就是执行mov ebp,esp 注意ebp上去,指向原来esp的位置,我这里怕被覆盖所以这样移,esp和ebp指的同一个位置
再执行sub esp 0E4h
这里就是进了main函数里了
push ebx
再来lea
即是load effective address
ecx就是一会儿存c大概进行那么多次
让这块空间全变成cccccc
PUSH 压栈:从栈顶放一个元素
POP 出栈:从栈顶拿走一个元素
所以有时访问越界时会打印烫烫烫就是这个原因。
接下来就是把局部变量的赋值
接下来又是调用函数Add,传参,ebp-14h就是b
mov eax,dword ptr[ebp - 14h]传参
mov ecx,dword ptr[ebp - 8]传参
call就是调用函数区
再接着又是调用函数开辟栈帧
计算
再接下来将结果带回去eax是个寄存器,寄存器不会因为调用结束就销毁
函数计算
得出c的结果,并销毁,main的同理
其实可以发现 在函数开辟空间上进行的,就是一个z=x+y操作,x,y都不在函数空间里,即可以理解为形参不在函数的栈帧里。