目录
程序的执行过程可看作连续的函数调用,而C语言中,每个栈帧对应着一个未运行完的函数 每个函数的每次调用(通常使用堆栈实现),都有它自己独立的一个栈帧 这个栈帧中保存了该函数的返回地址和局部变量维持着所需要的各种信息 所以栈帧也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构
栈帧就是一个函数执行的环境:函数参数、函数的局部变量、函数执行完后返回到哪里等
栈,也叫堆栈,是一种数据结构,具有先进后出的特点(类似子弹上弹夹) 在函数栈帧创建过程中,内存从高地址往低地址使用
寄存器edp存放了指向函数栈帧栈底的地址(高地址) 寄存器esp存放了指向函数栈帧栈顶的地址(低地址) esp和ebp共同维护函数栈帧
在VS2013下逐步调试add函数向大家展示并讲解栈帧的创建和销毁过程
int Add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
int ret = 0;
ret = Add(a, b);
printf("%d\n", ret)
return 0;
}
main函数是由__tmainCRTStartup函数调用的
对于vs来说__tmainCRTStartup函数也由其他函数调用(取决于编译器)
move(赋值) ebp,esp 意思是把esp的内容给ebp,ebp存了esp里面的内容
就是说ebp指向了main函数的栈底地址
esp减去0E4h大小的值(内存从高地址往低地址使用)
即ebp的上面开辟了0E4h这个大小的一个空间(即main函数空间)
esp指向main函数的栈顶地址(esp和ebp开始维护main函数空间)
把 39h存到ecx里面,把0cccccccch存到eax这个寄存器中(初始化main函数)
rep stos(重复拷贝),dword就是doubleword(word是两个字节,double就是4个字节)
从这里看来ecx以及eax寄存器起到将相关命令参数存放传递的作用
mov(赋值)命令将[ebp - 8]地址中的内容赋值给eax,并用push(压栈)将eax压入栈顶(相当于将b的值压入了栈顶)同样将[ebp - 4]地址中的内容赋值给ecx,并将ecx压入栈顶
这里也就是在给Add函数传参,那这里被压入栈顶的两个寄存器就相当于a,b的一份临时拷贝
call(声明函数返回地址)(不管是变量还是函数都在内存中存放,但因为内存的分配使得他们的地址并不连贯,所以为了程序执行的流畅,这里需要声明被调用函数执行完成后返回上一级函数的地址)在执行call指令同时,在栈顶ecx又开辟了一开新空间,用于存放call指令下一条指令的地址,便于返回
mov(赋值)将[ebp + 8]地址的内容赋值给eax,add(加法)将[ebp + 0Ch]地址的内容加给eax,然后mov(赋值)将eax内容赋值给[ebp - 4]地址的内容
函数内部并没有直接创建一个参数x,y,而是调用了传参过来的寄存器中的值
返回z的值时,mov(赋值)将z的值赋值到了eax中
从这里看来,函数返回值的传递实质上是通过寄存器传递的
函数结束,pop(出栈)指令,将edi, esi,ebx寄存器退出栈顶
并用mov(赋值)命令将ebp寄存器中的地址赋值给esp(ebp,esp是维护空间边界的两个寄存器,当他俩地址相遇时,代表这片空间消失)相当于将为Add函数开辟的空间销毁
再出栈ebp就回到了main函数的栈底
ret(返回)返回存放在当前栈顶的地址中的地址值,即回到了刚才Call(声明返回地址)指令的下一条指令处(即把形参也弹出去了)
add函数栈帧的创建和销毁就已经完成了