# 引言

调用约定(Calling Convention)是一种实现级别的方案,规定了被调函数如何从主调函数获取参数,以及如何将返回值传回主调函数。简单来说,就是参数和返回值如何传入传出。具体来讲,调用约定解决了以下问题:

  1. 参数、返回值、返回地址放在哪里?寄存器,调用栈,还是其他数据结构?
  2. 使用内存传递参数时,按照什么顺序传递实参?
  3. 如何将返回值传回主调函数?尤其是当返回时很复杂时,使用栈,还是寄存器,还是堆?
  4. 进行函数调用前以及函数调用结束后,由主调还是被调函数进行环境初始化和清理的工作?
  5. 用于描述参数的元数据是否传递?怎么传递?
  6. 在哪里保存帧指针?在栈帧,还是寄存器?
  7. 对于不属于函数局部变量的数据,在哪里保存静态域链接?
  8. 局部变量如何分配?

有时还有以下的区别:

  1. 哪些寄存器可以直接被被调函数使用,而不需要进行保护
  2. 哪些寄存器被认为是 volatile 的,如果是 volatile 的,不需要由被调函数恢复的

即便部分编程语言可能规定了调用约定,但是不同的实现方式可能仍然采用不同的调用约定。实现上可能提供多于一种的调用约定供选择。原因可能包括性能、和其他流行语言的频繁适配、技术或是非技术的原因等等。

# x86 架构微处理器调用约定

# stdcall 调用约定

# cdecl 调用约定

即 C declaration,起源于微软为 C 设计的编译器,被 x86 架构下很多 C 编译器使用。有以下特征:

  • 由主调函数清理栈上的参数
  • 使用栈进行参数传递
  • 使用 EAX 寄存器返回整数和内存地址,使用 ST0 x87 寄存器返回浮点数
  • EAX、ECX、EDX 寄存器由主调函数保存,其他寄存器由被调函数保存
  • 调用新的函数时,x87 浮点寄存器从 ST0 到 ST7 必须是空的,退出函数时 ST1 到 ST7 必须是空的,ST0 不用作返回值时也必须是空的
  • 函数传参时,从右向左压栈

对于如下的 C 源程序,

int callee(int, int, int);
int caller(void)
{
	return callee(1, 2, 3) + 5;
}

将被转换为如下的 x86 汇编代码(intel 语法),

caller:
    ; make new call frame
    ; (some compilers may produce an 'enter' instruction instead)
    push    ebp       ; save old call frame
    mov     ebp, esp  ; initialize new call frame
    ; push call arguments, in reverse
    ; (some compilers may subtract the required space from the stack pointer,
    ; then write each argument directly, see below.
    ; The 'enter' instruction can also do something similar)
    ; sub esp, 12      : 'enter' instruction could do this for us
    ; mov [ebp-4], 3   : or mov [esp+8], 3
    ; mov [ebp-8], 2   : or mov [esp+4], 2
    ; mov [ebp-12], 1  : or mov [esp], 1
    push    3
    push    2
    push    1
    call    callee    ; call subroutine 'callee'
    add     esp, 12   ; remove call arguments from frame
    add     eax, 5    ; modify subroutine result
                      ; (eax is the return value of our callee,
                      ; so we don't have to move it into a local variable)
    ; restore old call frame
    ; (some compilers may produce a 'leave' instruction instead)
    mov     esp, ebp  ; most calling conventions dictate ebp be callee-saved,
                      ; i.e. it's preserved after calling the callee.
                      ; it therefore still points to the start of our stack frame.
                      ; we do need to make sure
                      ; callee doesn't modify (or restores) ebp, though,
                      ; so we need to make sure
                      ; it uses a calling convention which does this
    pop     ebp       ; restore old call frame
    ret               ; return

手动指定调用约定: return_type __cdecl func_name();

# fastcall 调用约定

# thiscall 调用约定

# nakedcall 调用约定

# 参考资料

  1. https://en.wikipedia.org/wiki/X86_calling_conventions
  2. https://en.wikipedia.org/wiki/Calling_convention
更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

c01dkit 微信支付

微信支付

c01dkit 支付宝

支付宝

c01dkit qqpay

qqpay