调用约定
复习
- 目标代码生成:后端的总任务
- 函数调用与运行栈:调用时如何保存信息
- 寄存器分配:决定值的存放
TL;DR
- 调用约定规定函数之间怎样传参、怎样返回
- 它约定参数与返回值放哪里,以及谁负责保存旧值
- 调用双方必须遵守同一套约定
- 它让不同模块、甚至不同语言编译出的代码也能协作
正文
函数之间要互相调用。可一个函数被调用时,参数放哪、结果从哪拿、哪些寄存器要小心保护——这些如果没有统一规定,调用方和被调方就会“对不上暗号”。这套规定,就是调用约定(calling convention)。
约定些什么
调用约定通常要讲清几件事:
- 参数怎么传:放进特定的寄存器,还是压到栈上?顺序如何?
- 返回值怎么给:放在哪个寄存器或内存位置?
- 谁来保护寄存器:调用方还是要保存现场?
- 栈由谁清理:调用结束后,谁负责把栈恢复
其中“保护寄存器”有个重要区分:
- 调用者保存(caller-saved):调用方若还需要某个寄存器里的值,就得自己先存好
- 被调用者保存(callee-saved):被调函数若要改动某个寄存器,必须先用后恢复,保证调用方不受影响
为什么必须统一
调用约定最大的意义,是让“调用”这件事变成一个双方都懂的接口。
只要大家都遵守同一套约定:
- 一个函数可以安全地调用另一个
- 由不同程序员、甚至不同语言编译出来的模块,也能互相调用
- 库函数被任何人调用,都不会“读错参数”
这套“跨模块、跨语言的公共约定”,在系统层面常被称为 ABI(应用二进制接口)。它是比源码接口更底层的一种契约。
接口与实现分离的老思想,在这里又出现了一次:约定的接口稳定了,各路实现才能自由协作。
思考题 1
调用约定通常规定了哪些内容?
思考题 2
为什么调用约定必须由调用双方共同遵守?
小结
知识点
- 调用约定规定传参、返回值与寄存器保护等
- 区分调用者保存与被调用者保存
- 统一约定让不同模块、语言能互相调用
- 系统层面的这类约定常称为 ABI
参考资料
- Wikipedia(zh):调用约定:函数间如何传递参数与返回值
- Wikipedia(zh):应用二进制接口:二进制层面的跨模块接口约定
思考题答案(仅供参考)
思考题 1
它通常规定:参数通过哪些寄存器或栈传递、返回值放在哪里、哪些寄存器由调用方保存、哪些由被调用方保存,以及调用结束后由谁清理栈等内容。
思考题 2
因为调用是一个交互过程:调用方按约定放参数,被调方按约定取参数,返回时再按约定交结果、恢复寄存器与栈。只有双方遵守同一套约定,参数、返回值才不会被“理解错”,调用才能正确完成。
协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
封面图
设计师 | 南国微雪