在 c++++ 中,函数调用约定决定了函数参数和返回值的传递方式。现代约定包括传值(按值传递)和传引用(按引用传递)。常见的调用约定类型包括 cdecl、stdcall、fastcall 和 aapcs。传引用通常更有效率,因为它避免了不必要的拷贝。
C++ 函数调用约定的前世今生
在 C++ 中,函数调用约定决定了函数参数和返回值是如何在调用者和被调用者之间传递的。本文将深入探讨函数调用约定,从历史上发展到现代实践。
历史起源
立即学习“C++免费学习笔记(深入)”;
最初,C 语言没有显式的调用约定,这导致不同编译器之间的不兼容。为了解决这个问题,引入了 System V ABI(应用程序二进制接口),它指定了 x86 架构上的调用约定。
现代约定
现代调用约定通常分为两类:
- 传值:参数按值传递,被调用者无法修改原始值。
- 传引用:参数按引用传递,被调用者可以修改原始值。
C++ 支持通过引用或指针传递参数。对于大对象或可能被修改的参数,传引用通常更有效率,因为它避免了不必要的拷贝。
调用约定类型
传统的 x86 调用约定 (cdecl)
- 参数从右到左推入堆栈。
- 返回值从函数本身返回。
Microsoft x86 调用约定 (stdcall)
- 参数从右到左推入堆栈。
- 返回值通过 eax 寄存器返回。
- 调用者负责清理堆栈。
x64 调用约定 (fastcall)
- 前四个整数或指针参数通过 rcx, rdx, r8, r9 寄存器传递。
- 其余参数从右到左推入堆栈。
- 返回值从函数本身返回。
ARM64 调用约定 (aapcs)
- 前八个整数或指针参数通过 x0 到 x7 寄存器传递。
- 其余参数从右到左推入堆栈。
- 返回值从函数本身返回。
实战案例
考虑以下函数,它在传值的情况下交换两个整数:
void swap(int a, int b) {
int tmp = a;
a = b;
b = tmp;
}
如果使用传引用的调用约定,可以简化函数:
void swap(int& a, int& b) {
int tmp = a;
a = b;
b = tmp;
}
在传引用时,a 和 b 是指向原始值的引用,因此上面的交换操作可以修改原始值。