前期学习的时候,我们可能有很多疑惑?
比如:
局部变量是怎么创建的?
为什么局部变量的值是随机值?
函数是怎么传参的?传参的顺序是什么?
形参和实参是什么关系?
函数的调用是怎么做的?
函数调用结束后怎么返回的?
当我们知道函数栈帧的创建和销毁就都懂了,简单来讲就是修炼自己的内功。
进入正题
今天讲解的时候,使用环境是VS2013,不使用太高级编译器的原因是高级的编译器不容容易学习和观察。不同的编译器,函数调用过程中栈帧创建是有差异的,具体细节取决于编译器实现。
我们在理解函数栈帧之前必须先了解两个寄存器 ebp esp
edp,esp 这两个寄存器中存放的是地址
这两个地址是用户来维护函数的调用的。
接下来我们来解释一下
其实每一次函数的调用都需要为其开辟一段空间。
我们使用最简单的函数调用代码来为大家演示。
然后我为其开辟一段空间我使用简单点的方法让大家容易理解
这是一段地址我接下来给大家演示它们之间的关系
关键点拨:C语言\C++中 栈区---内存用来存放 局部变量,函数的形式参数(临时属性的变量)。 堆区---用来存放 malloc,free,calloc,realloc(动态内存管理)。 静态区---用来存放 静态内存,全局变量。
图中的esp,edp为main函数开辟了一段栈区用来维护main函数的运行,主函数从main函数开始执行
当执行到C=Add(a,b)时,函数向上执行此时esp和edp也维护着函数的调用。
知识拓展:栈区的使用方法从低到高使用。
所以不难看出地址的使用是从高地址到低地址。
此时esp维护着栈顶,edp维护着栈低。
当我们在VS2013的编译器下是实现这个代码的时候我们可以了解到main的调用
接下来只展示结果,过程过于复杂。
得到结论:
调用的两个函数函数栈帧的过程如下:
接下来我们为大家演示地址使用的过程(通过调试窗口)
下面有调试的过程
通过调试观察我们不难发现地址由8变成了4,这就意味着地址从高到低进行执行。
由于地址的跳动esp与edp也向上悦动,此时esp与edp之间的空间就是为main函数开辟的了。
接下来经过压缩(push)在esp的顶上又会出现一段空间
我们观察push(压缩)的有 ebx esi edi
那么我们不难通过图示来为大家讲解
由于不断地压缩esp为了维护栈区所以也在不断的移动由刚开始的指向ebx到现在的指向edi.
那么就有一个疑问开辟的空间的地址是否可以得到呢?
我们通过调试也不难得到main函数的栈帧中的地址
接下来为大家展示在此之前大家需要了解两个单词的意思
push 压栈---给栈顶放一个元素
pop 出栈---从栈顶删除一个元素
我们不难观察出在main函数栈帧中的地址是cc cc cc cc
有了上面的作为铺垫现在我开始真正意义上的进入正题。
我们拿 int a=10; 来为大家展示
move 移动的意思在这里指的是把0Ah(10)放入【ebp-8】的位置
那么是如何放进去的呢,其基本原理是什么呢?
大家好好看
我们默认是四个字节,那么edp向上移动一格此时edp指向的就是edp-4 以此类推那么edp-4的上面一格就是edp-8.
我们来解释一下,在最开始我们就强调了edp,esp,是两个寄存器用来存放地址的。
那么我们就可以得到结论在 int a=10;中为a开辟的空间就是 edp-8.
后面的就按照这个思路理解
这就是局部变量的创建的过程。
现在我们来总结一下创建的过程,首先创建一个函数栈帧,在这个函数栈帧中开辟空间,
然后创建。
创建好了之后我们来研究函数的调用。使用上面的代码是Add函数。
函数调用的第一步是传参,那么是如何传参的呢?
接下来为大家讲解其过程
我们来解读一下,我们可以知道【ebp-14】的值是20(上面有讲解),此时出现了push(压栈)push eax 这是什么意思呢?
其实很好理解来吧展示
因为压栈是在顶部给一个元素,那么我们在其顶部给个元素eax就很好理解了。
通过程序的执行又push了一个ecx.
好接下来我们继续观察程序的指令。下面展示过程
我们直接说结论吧
通过一系列的指令我们可以得出:形参就是实参的一份临时拷贝。
通过压栈的方式把参数传到栈帧里面,然后通过偏移量来找到参数。