结论先行,直接说结论
- API 是“怎么用”:开发者通过 API 编写代码,调用功能。
- ABI 是“怎么执行”:程序运行时,CPU 和操作系统根据 ABI 规则执行机器码。
1. 抽象层级:源代码 vs 二进制代码
-
API(Application Programming Interface)
- 源代码级别的接口:开发者通过 API 编写代码时,只需要知道函数名、参数类型、返回值等逻辑上的规则。
-
ABI(Application Binary Interface)
- 二进制级别的接口:当代码被编译成机器码后,程序如何与操作系统或其他库交互,需要遵循 ABI 的规则。
假设我们有两个文件:
1. add.c(定义一个加法函数)
// add.c
int add(int a, int b) {
return a + b;
}
2. main.c(调用 add 函数)
// main.c
#include <stdio.h>
extern int add(int a, int b); // 声明 add 函数
int main() {
int result = add(3, 5);
printf("Result: %d\n", result);
return 0;
}
场景 1:使用相同的 ABI 编译
我们使用 GCC 编译器(默认遵循 x86_64 的 System V ABI):
gcc -c add.c -o add.o
gcc -c main.c -o main.o
gcc add.o main.o -o program
./program
输出:
Result: 8
解释:
- API:
add(int a, int b)是源码级别的接口,开发者通过这个 API 调用函数。 - ABI:GCC 按照 x86_64 的 System V ABI 规则生成二进制代码:
- 参数
a和b通过寄存器RDI和RSI传递。 - 返回值通过寄存器
RAX返回。 - 所有代码和数据对齐方式符合 ABI 规范。
- 参数
场景 2:ABI 不兼容导致的问题
假设我们换一个编译器(比如 MSVC,遵循 Windows 的 x64 ABI),并重新编译 add.c:
# 使用 MSVC 编译 add.c(假设生成 add.obj)
cl /c add.c
然后尝试用 GCC 编译 main.c 并链接:
gcc main.c add.obj -o program
结果:
- 链接失败 或 运行时崩溃。
原因:
- API 一致:
add(int a, int b)的源码接口是相同的。 - ABI 不同:
- Windows x64 ABI 的参数传递规则不同(例如,前四个参数通过
RCX,RDX,R8,R9传递)。 - System V ABI 的参数传递规则不同(例如,前六个参数通过
RDI,RSI,RDX,RCX,R8,R9传递)。 - 由于参数传递方式不同,
main.c中的add(3, 5)会将参数错误地放入寄存器,导致add.obj中的函数无法正确接收参数。
- Windows x64 ABI 的参数传递规则不同(例如,前四个参数通过
2. 兼容性影响:编译错误 vs 运行时崩溃
-
API 的变化
- 如果 API 变化(例如函数名或参数类型被修改),源代码会无法编译。
-
ABI 的变化
- 如果 ABI 变化(例如函数参数传递方式或结构体内存布局被修改),已编译的程序可能在运行时崩溃。
3. 应用场景:开发者 vs 系统/硬件
-
API 的场景
- 开发者直接使用:API 是开发者编写代码时的“工具说明书”。
- 典型例子:
- 调用操作系统提供的 API(如 Linux 的
read()或write())。 - 使用第三方库的 API(如 Python 的
requests.get())。
- 调用操作系统提供的 API(如 Linux 的
-
ABI 的场景
- 隐藏在底层:开发者通常不直接操作 ABI,但程序的运行依赖它。
- 典型例子:
- 不同编程语言之间的调用(如 C 代码生成的库被 Python 调用,需保证数据布局一致)。
- 操作系统内核与驱动的交互(如驱动模块必须符合内核的 ABI 规范)。
4. 操作系统的角色
-
API 是操作系统对外提供的“功能清单”
- 例如,Linux 的
man 2 read文档描述了read()函数的 API:参数、返回值、错误码等。 - 开发者通过 API 编写代码,调用这些函数。
- 例如,Linux 的
-
ABI 是操作系统对内提供的“执行规则”
- 例如,
read()函数的实现需要遵循 x86 架构的 ABI:参数如何传递(如通过寄存器rdi、rsi、rdx),返回值如何处理(如通过寄存器rax)。 - 程序运行时,CPU 根据 ABI 的规则执行机器码。
- 例如,
总结对比表
| 特性 | API | ABI |
|---|---|---|
| 抽象层级 | 源代码级别 | 二进制代码级别 |
| 关注点 | 函数、参数、返回值等逻辑规则 | 数据类型布局、寄存器使用、参数传递等物理规则 |
| 兼容性影响 | API 变化 → 编译错误 | ABI 变化 → 运行时崩溃 |
| 开发者可见性 | 可见(通过文档或头文件) | 通常不可见(由编译器/链接器处理) |
| 典型场景 | 调用库函数、微服务接口 | 跨语言调用、动态库更新、系统调用 |