首先需要知道CPU所做的事就是不停地读取程序,分析程序,执行程序。。。还有我们对内存的操作都是以字节为单位。
既然读取程序是从内存上读取的
那么也要知道内存是怎么分配的,按照什么原则分配。这里先对内存做个初步介绍。
用一张图解释:
其中堆栈相对而生,堆区向上增长,栈区向下增长,栈区内存用完即销,堆区需要程序员自己申请并自己释放,若不释放则会造成内存泄漏,或者程序结束时自动释放(这就是为什么有时候需要重启程序或机器的原因)。
【什么是栈帧结构?】
我的理解中栈帧结构就是在程序执行期间,在内存中的栈区开辟和释放的空间。一个程序中每个函数在被调用之时都会有自己的栈帧结构,当被调用完之后就会被释放,这个栈帧结构由CPU中的EBP(栈底)和ESP(栈顶)寄存器来维护。
【CPU中存在一些寄存器】
EAP,EBP,ECP,EXP这些都是通用寄存器
EBP:栈底寄存器 ESP:栈顶寄存器
EIP,也称程序计数器,指向当前指令下一条指令的地址:
在调用一个函数的时候用的是汇编语言中的call命令,call命令的作用是保存当前指令的下一条指令的地址,可能有人会问为什么要保存?那我想问问你出去玩完回不回家?他又问为什么保存的是下一条指令的地址?难道你刚回家就又要出去干同样一件事吗?保存在哪里请继续往下看。
【下面用一个简单的程序来完整了解一下程序的调用过程】
#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 ret=ADD(a,b);
printf("%d\n",ret);
return 0;
}
【在以往,我们都错误地以为main函数是入口函数】
那么我们调用堆栈看看:
很明显,在main函数之前还有一个函数叫mainCRTStartup()
由此得出mainCRTStartup函数才是入口函数,而main函数只不过是被它调用的。
由于main函数实际上是被调用的所以它就有自己的栈帧结构。
【进入单步调试,仔细研究函数的每一个过程】
注:随时打开寄存器和内存注意观察。
查看程序的反汇编,按F11进入main函数,边按F11边观察寄存器和内存的变化,在调用函数之前暂停一会,
观察寄存器和内存:
找到指向栈顶和栈底的寄存器,再在内存中找到对应区间进行观察
【下面仔细观察形参实例化环节】
先是形参实例化的过程,仔细观察
在上面先是将a=10放到了ebp-4的内存上,然后给ebp-8这块内存起个名字叫b,并且其内容为10
在这里却先是ebp-8,然后才是ebp-4。
原因很简单,形参实例化的顺序是从右往左的。
【call命令】
按一下F11
汇编:
寄存器:
再按一下
汇编:
寄存器:
再按
从上面图中可以发现,执行到call命令的时候并不是一步就进入被调函数中,而是先保存当前命令的下一条指令的地址,然后再修改eip到jmp,再修改eip才进入到被调函数里面。
【被调函数的栈帧结构形成过程】
参考上文
【关于返回值的问题】
既然被调函数有返回值,但是返回值是局部变量,随着函数的调用结束栈帧结构不复存在,局部变量也被销毁,那么返回值是怎么返回到main函数中的?
发现了吗?返回时是本身销毁了,返回的是一份拷贝,在被调函数中计算结果,将结果存入寄存器eax,而return 返回的时候返回的是寄存器中的值,简而言之,返回值是通过寄存器返回的。