调用约定

在计算机科学中,调用约定(calling convention)是一种定义子过程从调用处接受参数以及返回结果的方法的约定。不同调用约定的区别在于:

  • 参数和返回值放置的位置(在寄存器中;在调用栈中;两者混合)
  • 参数传递的顺序(或者单个参数不同部分的顺序)
  • 调用前设置和调用后清理的工作,在调用者和被调用者之间如何分配
  • 被调用者可以直接使用哪一个寄存器有时也包括在内。(否则的话被当成ABI的细节)
  • 哪一个寄存器被当作volatile的或者非volatile的,并且如果是volatile的,不需要被调用者恢复

架构举例
x86 (32-位)
32-位版本的x86架构使用了很多不同的调用约定。由于有着少量的架构寄存器,以及在历史上聚焦于简单性和更小代码大小,很多x86调用约定传递实际参数于堆栈之上。返回值(或到它的指针)被返回在一个寄存器中。一些约定对前几个实际参数使用寄存器,这可以增进性能,特别是对于被频繁调用的短小而简单的(就是不调用其他例程的例程)。

例子调用:

push EAX ; 传递一些寄存器结果
push dword [EBP+20] ; 传递一些内存变量(FASM/TASM语法)
push 3 ; 传递一些常量
call calc ; 返回的结果将在EAX中

典型的被调用者结构如下,其中某些或全部(ret除外)指令在简单过程中总是可以被优化掉的:

calc:
push EBP ; 保存旧的帧指针
mov EBP,ESP ; 得到新的帧指针
sub ESP,localsize ; 为局部变量预留空间
.
. ; 进行计算,留下结果于EAX
.
mov ESP,EBP ; 释放局部变量的空间
pop EBP ; 复原旧的帧指针
ret paramsize ; 释放参数空间并返回

一些约定留下分配的参数空间,使用平常的而非。在这种情况下,调用者可以于此例子中,或者用其他方式处置对ESP的变更。

x86-64
64-位版本的x86架构,叫做x86-64、x64、AMD64或Intel 64,有两个公用的调用序列。一个调用序列是Microsoft定义的x64调用约定,用于Windows;另一个调用序列规定于System V AMD64 ABI,用于类Unix系统,并在改动后用于OpenVMS。由于x86-64比32-位x86有更多的通用寄存器,两个约定都传递一些实际参数于寄存器之中。

ARM (A32)
标准32-位ARM调用约定分配16个通用寄存器为:

  • r15:程序计数器(依据指令集规定)。
  • r14:。BL指令,用在子例程调用中,存储返回地址于此寄存器。
  • r13:堆栈寄存器。PUSH和POP指令在“Thumb”运行模态下只使用这个寄存器。
  • r12:过程调用内部(Intra-Procedure-call)暂存(scratch)寄存器。
  • r4 至 r11:局部变量。
  • r0 至 r3:传给子例程的实际参数值和从子例程返回的结果值。

如果返回值的类型对于放入r0至r3太大,或者其大小在编译时间不能静态的确定,那么调用者必须在运行时间为这个值分配空间,并传递到这个空间的一个指针于r0,

子例程必须保持(preserve)r4至r11和栈指针的内容,这可以通过在中将其保存至堆栈,接着使用它们为暂存空间,接着在中从堆栈复原它们。特别是,调用其他子例程的子例程,在调用这些其他子例程之前,“必须”将链接寄存器r14中的返回地址保存到堆栈。但是在返回之时,这样的子例程不需要将这个值返回到r14,它们只需要将这个值装载进入程序计数器r15。

ARM调用约定强制使用完全降序的堆栈。此外,栈指针必须总是4-字节对齐,并且在具有公开接口的函数调用上必须总是8-字节对齐。

调用约定导致“典型”的ARM子例程会:

  • 在前言中,将r4至r11压入到堆栈,并将在r14中的返回地址压入堆栈,这可以通过单一的STM指令完成;
  • 复制任何要传递的实际参数(在r0至r3中)到局部暂存寄存器(r4至r11);
  • 分配其他局部变量到剩余的局部暂存器(r4至r11);
  • 进行计算并按需要使用BL调用其他子例程,假定r0至r3、r12和r14会被保持;
  • 将结果放入r0;
  • 在结语中,将r4至r11从堆栈取出,并取出返回地址放入程序计数器r15,这可以通过单一的LDM指令完成。

ARM (A64)
64-位ARM(AArch64)调用约定分配31个通用寄存器为:

  • x31 (SP):栈指针或零寄存器,依赖上下文。
  • x30 (LR):过程,用于从子例程返回。
  • x29 (FP):帧指针。
  • x19 至 x28:被调用者保存。
  • x18 (PR):平台寄存器。用于某些操作系统特定的特殊功能,或一个额外的调用者保存寄存器。
  • x16 (IP0) 和 x17 (IP1):过程调用内部(Intra-Procedure-call)暂存(scratch)寄存器。
  • x9 至 x15:局部变量,调用者保存。
  • x8 (XR):间接返回值地址。
  • x0 至 x7:传递给子例程的实际参数值和从子例程返回的结果值。

所有以x开始的寄存器都由一个对应的前缀着w的32-位寄存器,比如32-位x0叫做w0。

类似的,32个浮点寄存器被分配为:

  • v0 至 v7:传入的实际参数值和从子例程返回的结果值。
  • v8 至 v15:被调用者保存,但只有底部64位需要被保持。
  • v16 至 v31:局部变量,调用者保存。

参见

  • X86调用约定,在Windows与Linux上的调用约定
  • 语言绑定

*

  • 名字修饰
  • 应用编程接口
  • 应用二进制接口
  • SWIG

参考文献

评论 (0)

  • 还没有评论,来抢沙发吧。