C++函数调用过程中栈区的变化
先简单说一下什么是栈帧
大多数机器的数据传递、局部变量的分配和释放通过操纵程序栈来实现。为单个过程(函数调用)分配的那部分栈称为栈帧。
栈帧 stack frame
定义:机器用栈来自传递过程参数,存储返回信息,保存寄存器为以后恢复及本地存储。
作用:用于控制和保存一个函数调用过程的所有信息的
组成:由两个寄存器组成的 -- 栈指针和帧指针。
- 栈指针%esp是指向栈帧的顶部,是可以移动的。%esp指向低地址
- 帧指针%ebp是指向栈帧的最底部,一般用他来访问栈里的元素。只要栈帧没有消失,帧指针是不会移动的。%ebp指向高地址
注意:栈的地址是从高地址往低地址延伸,函数每一次的调用都有自己独立的栈帧。
栈帧示意图
一般来说,我们将 %esp
到 %ebp
之间区域当做栈帧。并不是整个栈空间只有一个栈帧,每调用一个函数,就会生成一个新的栈帧。
在函数调用过程中,我们需要用栈保存以下数据:
- 调用函数需要知道在哪里获取被调用函数返回的值;
- 被调用函数需要知道传入的参数在哪里,返回的地址在哪里。
- 需要保证在被调用函数返回后,
%ebp
,%esp
等寄存器的值应该和调用前一致。
可以去看看函数的调用示例的汇编代码,这样会更快理解,这里有一些例子
- http://www.cs.virginia.edu/~evans/cs216/guides/x86.html
- https://www.cnblogs.com/clover-toeic/p/3755401.html
函数调用流程
简单的来说就是先将函数压栈,当函数调用结束后,再出栈
普通函数调用流程:
- 开辟栈帧空间
- 函数参数从右至左进行压栈
- 函数返回地址进行压栈
- 函数局部变量进行压栈
虚函数调用流程(大体)
- 查找this指针(也就是实例)的地址
- 根据this指针,查找虚函数表(函数指针数组)的地址
- 从虚函数表中,取出相应的函数地址
- 之后的调用和普通函数调用方式一致
为什么参数从右至左压栈
- 可以动态变化参数个数。通过栈堆分析可知,自左向右的入栈方式,最前面的参数被压在栈底。这样的话,除非知道参数个数,否则是无法通过栈指针的相对位移求得最左边的参数。这样就变成了左边参数的个数不确定,正好和动态参数个数的方向相反。
- 更符合习惯。 采用这种顺序,是为了让程序员在使用C/C++的“函数参数长度可变”这个特性时更方便。
-
- 什么是“函数参数长度可变”?printf就是一个例子,它的参数的个数就是可变的。比如,printf("%d %d %d",1,2,3),在采用从右向左的参数入栈顺序时,参数出栈顺序时"%d %d %d",1,2,3。如果采用从左向右的入栈顺序,则出栈顺序变为3,2,1,"%d %d %d",这样就不能提前知道会有多少个参数
Reference:
- https://www.cnblogs.com/sddai/p/9762968.html
- https://www.cnblogs.com/wiesslibrary/p/15727311.html