ABI(Application Binary Interface,应用二进制接口)是二进制层面的契约。它用于处理已经编译好的目标文件、静态库、动态库,之间如何互相调用、传递数据。

ABI 规定了什么

  • 调用约定(calling convention):参数和返回值放寄存器还是栈、顺序如何、谁负责清栈(x86 的 cdecl / stdcall / fastcall,x64 的 System V AMD64 / Microsoft x64)
  • 名字修饰(name mangling):同一个函数在不同 ABI 下会有不同的符号名
    • Itanium(GCC / Clang):void foo(int) → _Z3fooi
    • MSVC:void foo(int) → ?foo@@YAXH@Z
  • 对象布局:成员顺序、填充字节、基类子对象位置、vptr 位置(见 类的内存占用)
  • 类型大小与对齐:sizeof / alignof、位域布局
  • 虚函数表布局:虚函数的槽位顺序,决定 virtual 调用跳到哪里(见 多态)
  • 异常与 RTTI:栈展开表、type_info 的结构
  • 动态链接细节:符号可见性、SONAME、符号版本
  • 文件格式:ELF(Linux)、PE/COFF(Windows)、Mach-O(macOS)
对比维度 API(源码契约) ABI(二进制契约)
内容 头文件里的声明、类定义、文档里的语义约定 符号名、调用约定、对象布局、vtable、异常、动态链接
阶段 编译期 链接期 + 运行期

ABI 兼容是什么意思

ABI 兼容(binary compatible)指的是:用旧版本编译出来的程序,不重新编译,直接换成新版本的库也能正常运行。

  • 保证源码之间仍旧能通过编译的是 API 兼容,保证旧的二进制文件能直接用的才是 ABI 兼容
  • 动态库发布新版本时,ABI 兼容才能让用户直接替换 .so / .dll,否则只能重新编译整个程序

谁定义 ABI

C++ 标准不规定 ABI,只规定语义。具体二进制长什么样由实现决定:

平台 / 编译器 ABI
GCC / Clang(Linux、macOS) Itanium C++ ABI
MSVC(Windows) Microsoft C++ ABI
标准库 libstdc++ / libc++ / MSVC STL 各自实现,布局可能不同

因此同一份源码用 GCC 和 MSVC 编出来的二进制不能互相链接,甚至同一编译器换了标准库(libstdc++/libc++)也不行。

C 语言本身没有 name mangling,调用约定按平台固定,因此跨语言调用时经常用 C ABI 当作标准。

怎么保持 ABI 稳定

  • pImpl(编译防火墙):头文件里只放一个指向实现的指针,成员全藏在 .cpp 里;改实现不动头文件,对象大小也不变
  • 纯 C 接口:extern "C" 关掉 name mangling,调用约定按平台固定;配合不透明指针(typedef struct Foo Foo;)把实现藏起来(见 extern “C”)
  • 预留槽位:接口里留空的虚函数或私有成员,新功能往里填
  • 符号版本化:glibc 用 .symver 给同一符号保留多个版本(如 GLIBC_2.2.5),旧程序仍绑到旧实现
  • SONAME 升级:ABI 破坏时把 libfoo.so.1 升成 libfoo.so.2,让包管理器同时装两个版本
  • abi_tag:GCC / Clang 的 __attribute__((abi_tag("v2"))) 会把标签编进符号名,避免新旧 ABI 悄悄混用

工具

nm -D --defined-only libfoo.so | c++filt     # 看导出的符号
readelf -sW libfoo.so                        # 符号表 / 版本信息
objdump -T libfoo.so                         # 动态符号表
abi-dumper libfoo.so -o foo.abi              # 导出 ABI 描述
abi-compliance-checker -l foo -old old.abi -new new.abi
abidiff old.so new.so                        # libabigail 的 ABI diff

GCC 的 -fabi-version=n 可以指定遵循哪一版 Itanium ABI。