ABI 是什么?
ABI: Application Binary Interface(应用二进制接口)。
其实就是针对 编译器 和 链接器 的二进制级别的一些规范和约束,主要规范的内容有:
- 规定函数的调用顺序,也称为“调用约定”,规定了如何将“函数”转换成汇编代码。
- 规定库函数如何表示,主要对链接过程有指导作用。
- 规定可以使用什么类型的数据,这些数据如何对齐以及其他低级细节。
- ABI 还涉及到 OS 的内容,包括可执行文件的格式、虚拟地址空间布局等细节。
为什么会有 ABI ?
原因其实很简单,硬件架构、OS、编译工具链以及编程语言的发展和逐层抽象让大部分程序员可以不太在意底层程序的执行过程,而只需要负责编写表明业务逻辑的源代码。大部分程序员不需要在意并不意味着这部分不存在,实际上,这部分内容是通向二进制文件执行的必经之路。
通过上面的分析可以知道, ABI 这个概念基本上是由(硬件架构, OS, 编译工具链, 编程语言)
这个四元组决定的。
- 架构兼容性:
amd64
架构和arm64
架构对应的指令集不同,因而一个可执行文件要想在这两个架构上成功运行,就需要编译这两个架构的二进制文件(也就是交叉编译)。 - OS 兼容性:
windows(PE-COFF)
,linux(ELF)
和macos(MACH-O)
上规定的程序二进制文件格式不同,因而也需要为不同的 OS 编译不同的二进制文件。 - 编译工具链兼容性:这个我们平时遇到的比较多,常见原因是不同的编译器或不同的编译器版本的名字修饰规则不同,导致链接器在链接时找不到对应名字的库函数。
- 编程语言兼容性:C 语言中的一些基本内容如不同类型数据在内存中存放的形式,寄存器的使用形式等,以及 C++的众多特性:虚函数如何调用、虚表的内容和分布形式、template 如何实例化等等,都是 ABI 所需要规定的内容。
ABI-Compatible ?
ABI-compatible 允许编译好的目标代码可以无需修改或重新编译链接就能直接运行,而从上面举的例子就可以发现,ABI 兼容是一件很难做到的事情,光是架构和 OS 的不同就需要不同的目标文件了。
而编译工具链的兼容性容易做到吗?其实也不容易。目前主流的 C++编译工具链有gcc
, llvm(clang)
和msvc
,这三者之间对于名字修饰的规定都不同,因而一个用clang
编译的库函数是无法被一个用msvc
编译的main
文件调用的。当然,这里指的是默认进行名字修饰的情况,如果使用extern "C"
对函数进行修饰,从而要求编译器使用 C 语言的编译和链接规范进行处理就可以解决这个问题。
C++一直被诟病的原因之一就是二进制兼容性不好,对于小型项目而言使用同一种编译器进行编译可能可行,但是对于大型项目而言不太现实,库代码的提供者通常只是提供编译链接好的库,并不提供源代码,所以要想做到对于所有的编译器(的所有版本)都进行支持是一件困难且不太现实的事情。