应用二进制接口 (ABI) 是两个二进制程序模块之间的底层接口,通常存在于:
- 已编译的应用程序代码 与
- 操作系统、硬件 或 共享库 之间。
它定义了数据结构、函数调用 和 系统调用 在二进制层面如何表示和访问,从而使得分开编译的代码可以在运行时协同工作。
✅ 为什么需要 ABI?
在以下情况下需要 ABI:
- 程序的不同部分是分开编译的
- 例如,使用预编译库(如
.so,.dll,.a,.dylib)。
- 例如,使用预编译库(如
- 跨语言或跨编译器集成
- 例如,从 Rust 调用 C 代码,或 Dart 通过 FFI 调用原生代码。
- 与操作系统或硬件交互
- 系统调用遵循 ABI 规则,因此您的应用程序可以与内核或硬件通信。
- 确保可移植性和兼容性
- 更改库的 ABI(例如函数签名的布局)可能会破坏依赖的应用程序。
⚙️ ABI 具体规定了什么?
它定义了以下技术细节:
| 方面 | 描述 |
|---|---|
| 调用约定 (Calling convention) | 函数如何被调用:参数如何传递(在寄存器/栈中)、谁清理栈、返回值如何传递。 |
| 数据类型布局 (Data types layout) | 结构体 (structs)、联合体 (unions)、枚举 (enums) 的内存对齐 (alignment)、填充 (padding) 和大小 (size)。 |
| 名称修饰 (Name mangling) | 函数名在二进制文件中如何编码(C++ 使用修饰,C 语言不使用)。 |
| 寄存器使用 (Register usage) | 哪些寄存器用于传递参数、返回值,以及哪些寄存器必须被保留(preserved)。 |
| 栈布局 (Stack layout) | 局部变量和返回地址如何放置。 |
📌 示例 1:从 Rust 调用 C 函数
假设您有这样一个 C 函数:
// lib.c
int add(int a, int b) {
return a + b;
}
如果您将其编译成共享库 (libadd.so),并从 Rust 调用它:
// main.rs
extern "C" {
fn add(a: i32, b: i32) -> i32;
}
fn main() {
unsafe {
println!("3 + 4 = {}", add(3, 4));
}
}
🔸 这里的
"C"指定了 C ABI,因此 Rust 知道如何正确调用add—— 使用相同的调用约定、栈布局等。
如果没有 ABI,Rust 和 C 可能以不同方式表示整数、使用不同的调用约定、或将值放在不同的寄存器中——这将导致程序崩溃或数据损坏。
📌 示例 2:结构体布局不匹配
在 C 语言中:
struct A {
int x;
char y;
};
在内存中,它可能看起来像这样:
| 字段 | 字节 | 说明 |
|---|---|---|
x | 0-3 | int (4 字节) |
y | 4 | char (1 字节) |
| ---填充(padding)--- | 5–7 | 用于对齐 (alignment) |
如果另一种语言(例如 Python 的 ctypes 或 Go 的 FFI)在不知道填充规则的情况下尝试访问此结构体,它将错误地读取内存。ABI 定义了这种布局。
📌 示例 3:Flutter/Dart 通过 FFI 调用原生 C++
当 Dart 调用一个原生 C 函数时:
// native.c
double multiply(double a, double b) {
return a * b;
}
Dart 端使用 FFI:
final multiply = nativeLib
.lookupFunction<Double Function(Double, Double),
double Function(double, double)>('multiply');
Dart 使用 C ABI 来确保其调用函数的方式以及参数/结果的传递方式与 C 函数匹配。
📌 实际后果:ABI 破坏
如果您用 g++ 9.0 构建了一个 C++ 库,后来又用 g++ 13.0 构建,默认的 C++ ABI 可能已经改变,这可能导致:
- 段错误 (segmentation faults)
- 未定义行为 (undefined behavior)
- 链接器错误 (linker errors)
这就是为什么库维护者要承诺跨版本的 ABI 稳定性。
🧠 总结
| 概念 | 解释 |
|---|---|
| ABI | 组件之间的二进制级契约 (Binary-level contract)。 |
| 为何重要 | 支持跨模块、跨语言、跨编译器的协作。 |
| 定义内容 | 函数调用、内存布局、系统调用、寄存器使用方式等。 |
| 没有它会怎样 | 来自不同来源的编译代码将无法协同工作。 |