0.2 API和ABI的区别

365 阅读4分钟

结论先行,直接说结论

  • 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

解释:

  • APIadd(int a, int b) 是源码级别的接口,开发者通过这个 API 调用函数。
  • ABI:GCC 按照 x86_64 的 System V ABI 规则生成二进制代码:
    • 参数 ab 通过寄存器 RDIRSI 传递。
    • 返回值通过寄存器 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 中的函数无法正确接收参数。

2. 兼容性影响:编译错误 vs 运行时崩溃

  • API 的变化

    • 如果 API 变化(例如函数名或参数类型被修改),源代码会无法编译
  • ABI 的变化

    • 如果 ABI 变化(例如函数参数传递方式或结构体内存布局被修改),已编译的程序可能在运行时崩溃

3. 应用场景:开发者 vs 系统/硬件

  • API 的场景

    • 开发者直接使用:API 是开发者编写代码时的“工具说明书”。
    • 典型例子
      • 调用操作系统提供的 API(如 Linux 的 read()write())。
      • 使用第三方库的 API(如 Python 的 requests.get())。
  • ABI 的场景

    • 隐藏在底层:开发者通常不直接操作 ABI,但程序的运行依赖它。
    • 典型例子
      • 不同编程语言之间的调用(如 C 代码生成的库被 Python 调用,需保证数据布局一致)。
      • 操作系统内核与驱动的交互(如驱动模块必须符合内核的 ABI 规范)。

4. 操作系统的角色

  • API 是操作系统对外提供的“功能清单”

    • 例如,Linux 的 man 2 read 文档描述了 read() 函数的 API:参数、返回值、错误码等。
    • 开发者通过 API 编写代码,调用这些函数。
  • ABI 是操作系统对内提供的“执行规则”

    • 例如,read() 函数的实现需要遵循 x86 架构的 ABI:参数如何传递(如通过寄存器 rdirsirdx),返回值如何处理(如通过寄存器 rax)。
    • 程序运行时,CPU 根据 ABI 的规则执行机器码。

总结对比表

特性APIABI
抽象层级源代码级别二进制代码级别
关注点函数、参数、返回值等逻辑规则数据类型布局、寄存器使用、参数传递等物理规则
兼容性影响API 变化 → 编译错误ABI 变化 → 运行时崩溃
开发者可见性可见(通过文档或头文件)通常不可见(由编译器/链接器处理)
典型场景调用库函数、微服务接口跨语言调用、动态库更新、系统调用