手册:https://learn.microsoft.com/en-us/cpp/cpp/argument-passing-and-naming-conventions?view=msvc-160

手册:https://gcc.gnu.org/onlinedocs/gcc/x86-Function-Attributes.html#index-ms_005fabi-function-attribute_002c-x86

关于calling convention的制定(会在编译原理学习后进行补齐)

常见的calling convention(x86-32)

cdecl

特点

  1. 参数传递顺序
    • 参数从右到左依次压入堆栈。这意味着最后一个参数先被压入堆栈,第一个参数最后被压入堆栈。
  2. 堆栈清理
    • 调用者负责清理堆栈。在函数调用结束后,调用者必须调整堆栈指针以移除参数。这使得 cdecl 调用约定适用于可变参数函数(如 printf),因为调用者知道传递了多少参数。
  3. 名称修饰
    • 在编译后的目标代码中,函数名称前会加上一个下划线。例如,函数 int func(int a) 在目标代码中会被命名为 _func
  4. 适用性
    • cdecl 是 C 和 C++ 中最常用的调用约定,特别是在 x86 平台上。它是大多数编译器的默认调用约定。
  5. 兼容性
    • 由于调用者负责清理堆栈,cdecl 调用约定在不同编译单元和库之间具有较好的兼容性。
  6. 寄存器使用
    • 一般情况下, cdecl调用约定不会对寄存器使用做特殊规定,具体的寄存器使用策略会依赖于编译器的实现。

例子

#include <stdio.h>

// 使用 __attribute__((cdecl)) 指定 cdecl 调用约定
int __attribute__((cdecl)) func(int a, int b) {
    return a + b;
}

int main() {
    int result = func(3, 4);
    printf(Result: %d\n, result);
    return 0;
}
    pushl   $4                                                  
;参数的传递,并且是从右边往左边的
    pushl   $3
    call    func
    addl    $8, %esp                                                ;调用者清理堆栈
    movl    %eax, -12(%ebp) 返回结果

stdcall

stdcall 是一种调用约定(calling convention),主要用于 Windows API 函数。调用约定定义了函数调用过程中参数的传递方式、返回值的处理方式以及调用者和被调用者之间的责任分配。stdcall 具有以下几个主要特点:

  1. 参数传递顺序

    • 参数从右到左推入堆栈。这意味着最右边的参数首先被推入堆栈,最左边的参数最后被推入堆栈。
  2. 堆栈清理

    • 被调用函数负责清理堆栈。这与 cdecl 调用约定不同,cdecl 由调用者清理堆栈。
  3. 名称修饰

    • 函数名称在编译后会被修饰。具体来说,函数名称前面会加一个下划线 _,并在后面加上 @ 和参数的字节数。例如,函数 MyFunction 有两个 int 参数(每个 4 字节),在编译后会被修饰为 _MyFunction@8
  4. 寄存器使用

    • 一般情况下,stdcall 调用约定不会对寄存器使用做特殊规定,具体的寄存器使用策略会依赖于编译器的实现。
#include <stdio.h>

// 使用 __attribute__((stdcall)) 指定 stdcall调用约定
int __attribute__((stdcall)) func(int a, int b) {
    return a + b;
}

int main() {
    int result = func(3, 4);
    printf(Result: %d\n, result);
    return 0;
}
> diff stdcall.s cdecl.s
1c1
<    .file   stdcall.c
---
>   .file   cdecl.c
21c21
<    ret $8
---
>   ret
50a51
>   addl    $8, %esp

fastcall

fastcall 是一种调用约定,主要特点是尽可能使用寄存器来传递函数参数,以提高函数调用的效率。与其他调用约定相比,fastcall 通过减少对堆栈的依赖来加快函数调用速度。以下是 fastcall 调用约定的主要特点:

  1. 参数传递顺序

    • 前几个参数通过寄存器传递,剩余的参数从右到左推入堆栈。具体使用哪些寄存器取决于编译器和平台,但常见的做法是在 x86 平台上使用 ECXEDX 寄存器传递前两个参数。
  2. 堆栈清理

    • 被调用函数负责清理堆栈。这与 stdcall 调用约定类似。
  3. 名称修饰

    • 函数名称在编译后会被修饰。具体的修饰方式可能依赖于编译器,但通常会在名称前加一个 @ 符号。
  4. 寄存器使用

    • fastcall 调用约定明确规定了哪些寄存器用于传递参数,从而减少了堆栈操作,提高了性能。

常见的calling convention(x86-64

一般情况下使用System V AMD64 ABI