ABI(Application Binary Interface)是一种用于定义程序或库之间如何通过二进制代码进行交互的标准。它规定了如下几个方面:
- 数据类型的大小和对齐:定义基本数据类型(如整数、浮点数等)的大小和内存布局。
- 函数调用约定:定义函数如何接收参数和返回值,包括参数传递的顺序、使用的寄存器、栈清理责任等。
- 系统调用接口:定义应用程序如何通过系统调用与操作系统内核进行交互。
- 二进制格式:规定可执行文件、目标文件和动态库的二进制格式(如 ELF、PE 等)。
- 寄存器使用约定:规定哪些寄存器用于参数传递、哪些寄存器需要在函数调用后保持不变等。
- 异常处理和系统调用接口:特别是在涉及高级语言特性或系统级编程时,ABI 可能还包括异常处理机制和系统调用接口。
ABI 的存在使得不同编译器编译出的二进制代码可以互操作,在涉及到跨平台开发或者需要使用多种编程语言的项目中尤其重要。
C 语言的 ABI
当我们谈论 C 语言的 ABI 时,我们实际上是在讨论特定平台(硬件架构和操作系统)上的 C 程序的应用程序二进制接口。这并不是 C 语言标准的一部分,而是平台、编译器和操作系统共同定义的规则和约定。C 语言本身并不定义 ABI,因为它旨在保持最大的灵活性和可移植性,以便在多种硬件和操作系统上实现。
所以不像是由委员会讨论制定的语言规范一样,C-ABI 并不指代某一个具体的标准,根据平台不同都可能有区别。
以下是一个简单的表格,列出了不同平台的常见 ABI 规范。
架构/操作系统 | Linux | Windows | macOS |
x86 | System V ABI | cdecl, stdcall | 不常用 |
x86-64 | System V ABI | Microsoft x64 ABI | System V ABI |
ARM | ARM EABI | ARM (WinCE) | ARM64 ABI |
ARM64 | AArch64 ABI | Microsoft ARM64 ABI | ARM64 ABI |
PowerPC | PowerPC ELF ABI | 不常用 | 不常用 |
RISC-V | RISC-V ELF ABI | 不常用 | 不常用 |
- System V ABI:这是大多数 Unix 系统(包括 Linux 和 macOS)在 x86 和 x86-64 架构上使用的 ABI。
- Microsoft x64 ABI:这是 Windows 在 x86-64 架构上使用的 ABI。
- ARM EABI 和 AArch64 ABI:这些是 ARM 架构的标准 ABI,分别用于 32 位和 64 位。
- cdecl 和 stdcall:这些是 Windows 在 x86 架构上常用的调用约定。
- RISC-V ELF ABI:这是 RISC-V 架构的标准 ABI,用于 ELF 格式的可执行文件。
这些不同的 ABI 之间可能存在不兼容,需要通过特定的接口或工具进行转换和适配。
理解 extern "C"
包括 C++ 和 Rust 在内的很多编程语言都使用
extern "C" 导出符合 C 语言标准的函数,通常它会做这些事:1. 禁用 C++ 名称修饰:在 C++ 中,函数名通常会被编译器进行名称修饰(name mangling),以支持函数重载和其他特性。
extern "C" 指示编译器不要对这些函数进行名称修饰,而是使用 C 的命名约定。这使得编译后的函数名在链接时可以被 C 语言或其他支持 C ABI 的语言正确识别。2. 使用 C 的函数调用约定:
extern "C" 确保函数使用 C 语言的函数调用约定。这包括参数传递的方式、返回值的处理、栈的清理等。这种约定与 C++ 或其他语言的调用约定可能不同,因此这是确保跨语言调用时正确性的关键。3. 跨语言链接:使用
extern "C" 可以让其他语言(如 Python、Rust、Fortran 等)通过 C 的 ABI 调用这些函数。由于所有平台几乎都会支持 C 语言并定义其在自己平台上的 ABI,因此使用 C-ABI 可以确保在任何平台上都能使用一套稳定的 ABI 来提供跨语言的二进制兼容性。因此通过
extern "C" 导出函数,可以提高函数接口的兼容性和可移植性,使得这些函数可以被其他语言或库轻松调用。