提示:
1、这里使用的环境是VS2013,不高级的编译器容易学习和观察。
2、在不同的编译器下函数调用过程中栈帧的创建和销毁时略有差异的,具体细节取决于编译器的实现。
讲栈帧——先看寄存器
寄存器有eax,ebx,ecx,edx,ebp,esp。
其中ebp,esp这两个寄存器中存放的是地址,这两个地址是用来维护函数栈帧的。这两个寄存器看成指针。
每一个函数调用都要在栈区创建一个空间。
压栈:给栈低放一个元素——push
出栈:从栈顶删除一个元素——pop
在还没有调用Add函数的时候就已经把参数a、b传过去了,先传的是b,后传的是a。(从左向右传参),所以参数先进入栈区压栈,等真正进入函数内部后,用x和y相加的时候是找回了之前压栈的这两个参数,计算和后放到z中。
①局部变量是怎么创建的?
答:首先为函数分配好栈帧空间,栈帧空间里初始化一部分函数之后然后给局部变量栈帧里面分配空间。
②为什么局部变量的值是随机值?
答:因为随机值是随机放进去的。如果给局部变量初始化就把随机值覆盖了。
③函数是怎么传参的?传参的顺序是怎样的?
答:当调用函数的时候,在还没调用的时候就已经push把参数从右向左开始压栈压进去,当真正进入函数的形参时,其实在Add函数栈帧里通过指针的偏移量找回形参。
④形参和实参是什么关系?
答:形参是函数压栈开辟的空间,它和形参在值上是相同的,空间是独立的,所以形参是实参的临时拷贝,改变形参不会影响实参。
⑤函数调用是怎么做的?函数调用是结束后怎么返回的?
答:在函数调用之前就已经把call指令下一条的地址记住了(存储——压进去),把ebp调用这个函数的上一个函数的栈帧的ebp就存进去了,当函数调用完要返回的时候,弹出ebp就能找到原始上一个函数调用的ebp了,然后指针往下走的时候就能找到esp的地址回到栈帧空间,因为记住了call指令下一条的地址,往回返的时候就可以跳转到call指针的下一条地址让函数调用返回值可以返回。返回值是怎么带回来的呢?是通过寄存器的方式带回来的。
注意函数调用的返回值是放到寄存器中了不会销毁。
函数内部创建的静态变量是在全局开辟的。
ebp不是数据结构里的头指针,与它没什么关系。ebp就是个寄存器,它是用来记录当前调用函数栈帧的栈低的位置的地址。ebp是寄存器,pop ebp的意思是栈顶上的元素弹出来放到ebp中。
在栈区给每个函数开辟的空间是不一样的,不确定。
寄存器不在main()函数里,寄存器不是内存中的,是独立于内存的,是集成到CPU上的。电脑上的存储有硬盘,内存,寄存器,这些都是完全独立的,不同的空间。
ebp和esp是存放地址的,这个地址是用来维护栈帧的,ebo和esp里村的是这个栈空间的维护当前函数调用所使用栈空间的栈低和栈顶的,ebp指向栈低esp指向栈顶,ebp和esp没函数的地址没有关联。
形参的创建的空间在main函数和调用函数之间。函数的形参不在该函数的栈帧里面,可以理解为放在main函数栈帧里,相当于main函数栈帧的拓宽。