函数调用过程概述
stack v. 堆木头
- 栈:在函数调用过程中,需要定义一个栈,栈实际上就是一块内存,这块内存用来存放函数调用过程中它所使用的一些局部变量
- 栈帧:每次一个函数要执行的时候,我们就要为这个函数分配一块空间来存放它自己的函数栈(即入栈),即栈帧。当函数返回时,栈帧会被释放掉(即出栈)。
函数调用过程中有关寄存器的编程规定
寄存器名 | ABI 名(编程用名) | 用途约定 | 谁负责在函数调用过程中维护这些寄存器 |
---|---|---|---|
x0 | zero | 读取时总为 0,写入时不起任何效果 | N/A |
x1 | ra | 存放函数返回地址的值(return address) | Caller(调用者) |
x2 | sp | 存放栈指针(stack pointer) | Callee(被调用者) |
x5 |
t0 |
临时(temporaries)寄存器,Callee 可能会使用这些寄存器,所以 Callee 不保证这些寄存器中的值在函数调用过程中保持不变,这意味着对于 Caller 来说,如果需要的话,Caller 需要自己在调用 Callee 之前保存[^1]临时寄存器中的值 | Caller(调用者) |
x8 |
s0,s1,s2~s11 | 保存(saved)寄存器,Callee 需要保证这些寄存器的值在函数返回后仍然维持函数调用之前的原值,所以一旦 Callee 在自己的函数中会用到这些寄存器则需要在栈中备份并在退出函数时进行恢复 | Callee(被调用者) |
x10,x11 | a0,a1 | 参数(argument)寄存器,用于在函数调用过程中保存第一个和第二个参数,以及在函数返回时传递返回值 | Caller(调用者) |
x12~x17 | a2~a7 | 参数(argument)寄存器, 如果函数调用时需要传递更多的参数,则可以用这些寄存器,但注意用于传递参数的寄存器最多只有 8 个(a0 ~ a7),如果还有更多的参数则要利用栈 | Caller(调用者) |
- [^1]: 保存到 Caller 的函数栈里。
函数调用过程中实现被调用函数的编程约定
- 函数起始部分(Prologue)
- 减少 sp 的值,根据本函数中使用 saved 寄存器的情况以及 local 变量的多少开辟栈空间。
- 将 saved 寄存器的值保存到栈中
- 如果函数中还会调用其他的函数,则将 ra 寄存器的值保存到栈中
- 函数执行体
- 从栈中恢复 saved 寄存器
- 如果需要的话,从栈中恢复 ra 寄存器
- 增加 sp 的值,恢复到进入本函数之前的状态。
- 调用 ret 返回