手册:https://learn.microsoft.com/en-us/cpp/cpp/argument-passing-and-naming-conventions?view=msvc-160
关于calling convention的制定(会在编译原理学习后进行补齐)
常见的calling convention(x86-32)
cdecl
特点
- 参数传递顺序:
- 参数从右到左依次压入堆栈。这意味着最后一个参数先被压入堆栈,第一个参数最后被压入堆栈。
- 堆栈清理:
- 调用者负责清理堆栈。在函数调用结束后,调用者必须调整堆栈指针以移除参数。这使得
cdecl
调用约定适用于可变参数函数(如printf
),因为调用者知道传递了多少参数。
- 调用者负责清理堆栈。在函数调用结束后,调用者必须调整堆栈指针以移除参数。这使得
- 名称修饰:
- 在编译后的目标代码中,函数名称前会加上一个下划线。例如,函数
int func(int a)
在目标代码中会被命名为_func
。
- 在编译后的目标代码中,函数名称前会加上一个下划线。例如,函数
- 适用性:
cdecl
是 C 和 C++ 中最常用的调用约定,特别是在 x86 平台上。它是大多数编译器的默认调用约定。
- 兼容性:
- 由于调用者负责清理堆栈,
cdecl
调用约定在不同编译单元和库之间具有较好的兼容性。
- 由于调用者负责清理堆栈,
- 寄存器使用:
- 一般情况下, 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
具有以下几个主要特点:
-
参数传递顺序:
- 参数从右到左推入堆栈。这意味着最右边的参数首先被推入堆栈,最左边的参数最后被推入堆栈。
-
堆栈清理:
- 被调用函数负责清理堆栈。这与
cdecl
调用约定不同,cdecl
由调用者清理堆栈。
- 被调用函数负责清理堆栈。这与
-
名称修饰:
- 函数名称在编译后会被修饰。具体来说,函数名称前面会加一个下划线
_
,并在后面加上@
和参数的字节数。例如,函数MyFunction
有两个int
参数(每个 4 字节),在编译后会被修饰为_MyFunction@8
。
- 函数名称在编译后会被修饰。具体来说,函数名称前面会加一个下划线
-
寄存器使用:
- 一般情况下,
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
调用约定的主要特点:
-
参数传递顺序:
- 前几个参数通过寄存器传递,剩余的参数从右到左推入堆栈。具体使用哪些寄存器取决于编译器和平台,但常见的做法是在 x86 平台上使用
ECX
和EDX
寄存器传递前两个参数。
- 前几个参数通过寄存器传递,剩余的参数从右到左推入堆栈。具体使用哪些寄存器取决于编译器和平台,但常见的做法是在 x86 平台上使用
-
堆栈清理:
- 被调用函数负责清理堆栈。这与
stdcall
调用约定类似。
- 被调用函数负责清理堆栈。这与
-
名称修饰:
- 函数名称在编译后会被修饰。具体的修饰方式可能依赖于编译器,但通常会在名称前加一个
@
符号。
- 函数名称在编译后会被修饰。具体的修饰方式可能依赖于编译器,但通常会在名称前加一个
-
寄存器使用:
fastcall
调用约定明确规定了哪些寄存器用于传递参数,从而减少了堆栈操作,提高了性能。
常见的calling convention(x86-64)
一般情况下使用System V AMD64 ABI