Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

调用约定

复习

  • 目标代码生成:后端的总任务
  • 函数调用与运行栈:调用时如何保存信息
  • 寄存器分配:决定值的存放

TL;DR

  • 调用约定规定函数之间怎样传参、怎样返回
  • 它约定参数与返回值放哪里,以及谁负责保存旧值
  • 调用双方必须遵守同一套约定
  • 它让不同模块、甚至不同语言编译出的代码也能协作

正文

  函数之间要互相调用。可一个函数被调用时,参数放哪、结果从哪拿、哪些寄存器要小心保护——这些如果没有统一规定,调用方和被调方就会“对不上暗号”。这套规定,就是调用约定(calling convention)。

约定些什么

  调用约定通常要讲清几件事:

  • 参数怎么传:放进特定的寄存器,还是压到栈上?顺序如何?
  • 返回值怎么给:放在哪个寄存器或内存位置?
  • 谁来保护寄存器:调用方还是要保存现场?
  • 栈由谁清理:调用结束后,谁负责把栈恢复

  其中“保护寄存器”有个重要区分:

  • 调用者保存(caller-saved):调用方若还需要某个寄存器里的值,就得自己先存好
  • 被调用者保存(callee-saved):被调函数若要改动某个寄存器,必须先用后恢复,保证调用方不受影响

为什么必须统一

  调用约定最大的意义,是让“调用”这件事变成一个双方都懂的接口

  只要大家都遵守同一套约定:

  • 一个函数可以安全地调用另一个
  • 由不同程序员、甚至不同语言编译出来的模块,也能互相调用
  • 库函数被任何人调用,都不会“读错参数”

  这套“跨模块、跨语言的公共约定”,在系统层面常被称为 ABI(应用二进制接口)。它是比源码接口更底层的一种契约。

  接口与实现分离的老思想,在这里又出现了一次:约定的接口稳定了,各路实现才能自由协作。

思考题 1

  调用约定通常规定了哪些内容?

思考题 2

  为什么调用约定必须由调用双方共同遵守?

小结

知识点

  • 调用约定规定传参、返回值与寄存器保护等
  • 区分调用者保存与被调用者保存
  • 统一约定让不同模块、语言能互相调用
  • 系统层面的这类约定常称为 ABI

参考资料

  1. Wikipedia(zh):调用约定:函数间如何传递参数与返回值
  2. Wikipedia(zh):应用二进制接口:二进制层面的跨模块接口约定

思考题答案(仅供参考)

思考题 1

  它通常规定:参数通过哪些寄存器或栈传递、返回值放在哪里、哪些寄存器由调用方保存、哪些由被调用方保存,以及调用结束后由谁清理栈等内容。

思考题 2

  因为调用是一个交互过程:调用方按约定放参数,被调方按约定取参数,返回时再按约定交结果、恢复寄存器与栈。只有双方遵守同一套约定,参数、返回值才不会被“理解错”,调用才能正确完成。

协议

  本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

封面图

设计师 | 南国微雪