为什么需要它?

不同的编程语言之间,想要构成调用与被调用的关系,必然需要有一套“约定”或者“规则”指导,二者遵守这个约定来协调工作。

定义

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 库)。
模块化/解耦独立分发链接供应商可以只提供二进制库,保护知识产权。
性能基准内存和调用效率影响函数调用和数据访问的执行速度
运行时一致性异常、类型信息和虚函数。确保复杂的运行时行为在所有模块中都是可预测和正确的。