为什么需要它?
不同的编程语言之间,想要构成调用与被调用的关系,必然需要有一套“约定”或者“规则”指导,二者遵守这个约定来协调工作。
定义
Application Binary Interface(ABI) 是指不同编程语言编写的模块之间,进行底层通信和数据交换所必须遵守的一套规则和约定。
当一个编程语言(或其编译出的代码)要被另一个编程语言(或其编译出的代码)调用时,ABI 规定了它们如何以二进制的形式协同工作。
核心内容
- 数据类型表示:不同语言中基本数据类型(如整数、浮点数)在内存中的大小和布局。
- 函数调用约定 (Calling Convention):
- 参数传递:参数是以什么顺序、通过寄存器还是栈来传递给被调用函数。
- 返回值传递:函数的结果是如何返回的。
- 栈管理:谁负责清理函数调用过程中在栈上分配的空间(调用者 Caller 还是被调用者 Callee)。
- 内存布局:对象、结构体和类在内存中的对齐和填充方式。
- 符号修饰 (Name Mangling):编译器如何将源代码中的函数名、变量名转换为链接器可识别的唯一二进制名称,这对于 C++ 等支持函数重载的语言尤其重要。
由以上的核心内容,带来了如下的属性
关键属性
1. 语言互操作性 (Language Interoperability)
这是 ABI 最直接、最核心的作用。
- 定义标准接口:ABI 提供了不同编程语言编译出的二进制模块进行通信的通用标准。通过遵循一套共享的 ABI,例如许多系统使用与 C 语言兼容的 ABI 作为通用标准(通常通过
extern "C"实现),可以实现 跨语言调用 (Foreign Function Interface, FFI)。 - 跨编译器/工具链协作:即使是同一门语言,由不同供应商的编译器(如 GCC、Clang、MSVC)编译出的代码,只要它们遵守相同的 ABI 约定,就可以相互链接和调用。这使得项目可以使用多种工具链构建,大大增加了灵活性。
2. 模块化与解耦 (Modularity and Decoupling)
ABI 允许系统和应用程序以模块化的方式构建和分发。
- 独立编译/分发:有了稳定的 ABI,库的供应商可以分发其库的二进制版本(例如
DLL或SO文件),而无需提供源代码。用户可以使用任何兼容的编译器,将自己的代码与这些二进制库链接起来。 - 版本升级隔离:如果一个库的 ABI 稳定,那么库的内部实现可以升级、优化或修改,只要不改变外部暴露的 ABI,调用方应用程序就无需重新编译,只需替换新的二进制库文件即可。
从这个角度来考虑为什么 C API 优于 C++ API
3. 性能优化基础 (Basis for Performance Optimizations)
ABI 对代码的运行时性能有直接影响。
- 调用约定效率:ABI 明确了函数参数如何通过寄存器和栈传递。高效的调用约定(如在寄存器中传递参数)可以减少内存操作,提升函数调用的速度。
- 内存布局优化:ABI 规定了数据结构(如结构体和类)在内存中的对齐和填充。正确的对齐可以确保处理器高效地读取数据,避免额外的内存访问周期。
4. 异常处理/运行时支持 (Exception Handling and Runtime Support)
ABI 必须涵盖复杂的运行时特性,以确保不同模块之间的行为一致。
- 异常传播:ABI 定义了异常如何在调用栈中跨越不同模块(甚至跨越不同语言编写的模块)正确地捕获和传播。
- 运行时类型信息 (RTTI):对于 C++ 等语言,ABI 规定了类型信息在二进制文件中的存储和访问方式,这是实现动态类型转换和虚函数调用的基础。
总结
| 属性名称 | 核心关注点 | 对开发和维护的影响 |
|---|---|---|
| 二进制兼容性 | 升级不破环现有调用。 | 库可以升级内部实现,应用程序无需重新编译。 |
| 语言互操作性 | 跨语言通信。 | 允许一个项目使用多种语言编写的模块(如 Python 调用 C 库)。 |
| 模块化/解耦 | 独立分发和链接。 | 供应商可以只提供二进制库,保护知识产权。 |
| 性能基准 | 内存和调用效率。 | 影响函数调用和数据访问的执行速度。 |
| 运行时一致性 | 异常、类型信息和虚函数。 | 确保复杂的运行时行为在所有模块中都是可预测和正确的。 |