函数调用栈的过程(图解)

前置知识

1、对进程的内存空间布局有一定了解,知道栈是从高地址往低地址增长。

2、对于x86架构处理器,当函数的局部变量和参数较少时,它们会保存在寄存器而不是内存的栈中,因为访问寄存器会更快。但是当寄存器不足以存放本地数据或对局部变量使用地址运算符&(内存引用)时,系统会在函数调用栈中为函数分配一个栈帧,栈帧中存储了函数的局部变量、参数和返回地址。

3、在x86(32位)中,寄存器%ESP是栈顶指针,指向栈顶元素。寄存器%EBP是栈底指针,指向当前函数的栈帧的栈底。所以对于一个函数的栈帧,它的范围就是栈底指针到栈顶指针之间的空间,如下图

0a296340cbe6403fbdbc0de17b8fe6a3.png

具体例子

以下面程序作为例子,main是调用函数caller,sum是被调用函数callee。按该程序来用图表示函数调用和返回过程中,栈空间的内存变化

cba13608dd0e4c2c99df15f490fe61e6.png

1、main函数栈帧初始状态:此时%ESP和%EBP都指向main函数栈底,此时栈中的值是调用main函数的函数的栈底地址(看不懂没关系,后面会理解)

f2057c67b2c240ebb5888633cb68e70b.png

2、先后将局部变量和要传入sum的参数压栈

d9fd1d0edfa2435ea1cca368ab7b0eb5.png

3、main函数调用sum,会执行汇编的call指令。该指令会先把当前函数的返回地址压栈,以便让程序能在sum函数执行完返回到main函数继续执行。然后程序计数器就跳转到sum函数那里去执行代码

32e050b2bc8d498085b9a8a8d0c1a102.png

4、执行sum函数。sum函数会先将当前的%EBP的值压栈(也就是main函数的%EBP值)。这一步是用来保存main函数的栈底指针,以便sum函数结束返回到main函数的栈帧状态

ce14645a8f7a405f9f9fbf5fe1515f18.png

5、将%EBP移动到%ESP,此时栈底指针%EBP就成了sum函数的栈底指针。所以从Caller's EBP开始到下面的内容就是sum函数的栈帧

6be1c4762aba4498b07ce85fc86aacc8.png

6、将sum的ret变量和temp临时变量压入栈中

f1732dbdc5f54a52a69298754696fce7.png

7、如果sum函数还要调用其他函数,就会像main一样先后压入返回地址和当前EBP。但现在sum函数结束了,将栈顶指针%ESP返回到栈底指针%EBP,ret和temp变量被废弃掉。(返回值ret的值会通过寄存器来传给main函数)

e6db7969a2e34dee8c687b239de1875b.png

8、sum函数执行出栈操作,将当前栈顶指针的值即Caller's EBP赋值给%EBP指针,这一步就会让栈底指针指回main函数的栈底

2e1b62b3cfe34515837a13f67dc771c8.png

9、此时ESP又进行出栈将返回地址赋值给程序计数器,那么程序就会回到返回地址继续执行main函数下面的语句。此时栈帧又恢复成main的栈帧,调用栈的变化过程结束。(可以看到此时栈内存中还留着之前函数的内容,这解释了未初始化变量的值是随机的)

b1a4c36e949f4786a40ecbdd14096aaa.png

注:上面的过程省去了一些细节,但不影响整体的理解

总得来说,函数调用栈的变化过程就是

1、将函数参数和返回地址压入栈中

2、将调用函数(main)的栈底指针压入栈中,然后就把被调用函数的变量压入栈中。

3、被调用函数执行完后,栈顶指针%ESP回到栈底指针%EBP处。然后将栈中的值(调用函数的栈底地址)弹出赋值给%EBP。栈底指针恢复为main函数栈底,然后继续将栈中的值(返回地址)弹出赋值给程序计数器,然后程序就回到main原来的地址那里继续执行main函数代码。

最后

补充

函数参数传递方式:在X86的32位系统中,参数都使用栈来传递。在X86的64位系统中,前6个参数使用寄存器来传递,剩下来的用栈传递。而在ARM32中(32位系统),前四个整数(包括指针)会用r0~r3寄存器传递,剩下的通过栈来传递,如果是浮点数或者结构体就直接通过栈来传递。ARM64同理,前8个整数(包括指针)会用x0~x7寄存器传递。

参考

如果想对照着汇编语言来看整个过程,可以看这个视频:065.回顾c语言函数调用的栈帧变化过程_哔哩哔哩_bilibili 。这个视频是根据汇编来讲整个过程,这篇文章其实是对这个视频的简化版,注重于理解过程。

  • 21
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值