C语言是否有标准的ABI?

47

来自其他地方的讨论:

C++没有标准ABI(应用程序二进制接口)。

但是C也没有,对吧?

在任何给定的平台上,它基本上都有。如果缺少ABI,它就不会成为跨语言通信的共同语言。

你对此有什么看法?


11
我认为,“在任何给定平台上”这个词有点含糊其辞,可能是无心之失。你可以把“平台”定义为“始终使用相同的C ABI”。具体来说,任何已定义的动态加载代码格式都必须定义跨可执行文件边界调用的ABI。该ABI必须支持C才能有任何实际用途(假设人们将以C或C ++编写可执行文件),并且可能也支持C ++,如果你很幸运的话。如果它支持C但不支持C ++,则您需要在发布的所有内容上都使用extern "C"来编写C ++,和/或使用相同的编译器对链接的可执行文件进行编译。 - Steve Jessop
还可以看到一个不同但相似的问题:如果定义了标准ABI,C/C++会失去什么? - Hibou57
9个回答

52

C语言没有定义ABI。事实上,它竭尽全力避免定义ABI。和我一样花费了大部分时间在8位字节、二进制补码算术和平坦地址空间下使用C语言编程的人们,在阅读当前C标准中复杂的语言时通常会感到惊讶。

例如,看看有关指针的内容。标准并没有像“指针是一个地址”这样简单地说明,因为那样会对ABI进行假设。特别是,它允许指针存在于不同的地址空间并具有不同的宽度。

ABI是一种从语言的执行模型映射到特定机器/操作系统/编译器组合的方式。在语言规范中定义它是没有意义的,因为那会导致某些架构上的C实现被排除在外。


6
在我写这个答案的时候,当前的C标准是C99。我怀疑在C11中没有太多变化。小型、巨型和中型内存模型不是C的一部分,而是8086架构上大多数C实现的一部分。 - JeremyP
@curiousguy 嗯,它可以是一个数组的索引,也可以是一个重定向到地址表的句柄,或者它可以是一个段和一个偏移量,或者它可以是一个带标签的描述符。C标准并没有说它必须是一个地址。 - JeremyP
@JeremyP 一个段偏移量决定一个地址;与纯地址的差别在于可能有多种方式指向同一地址,但它仍然编码了一个地址。地址表似乎与指针算术不兼容。 - curiousguy
1
@curiousguy 如果你要说任何可以解码成地址的东西都是地址,那么除了字面值之外,一切都是地址。你让这个词几乎失去了意义。 - JeremyP
1
@curiousguy 为什么不呢? - JeremyP
显示剩余4条评论

20

原则上,C语言没有标准的ABI,但在实践中,这很少有影响:你需要做你的操作系统供应商所做的事情。

以x86 Windows上的调用约定为例,Windows API使用所谓的“标准”调用约定(stdcall)。因此,任何想要与操作系统进行接口编程的编译器都需要实现它。然而,stdcall不支持所有的C90语言特性(如调用没有原型的函数、可变参数函数等)。由于微软提供了一个C编译器,第二个调用约定是必要的,称为“C”调用约定(cdecl)。大多数在Windows上的C编译器将其作为默认的调用约定,因此是可互操作的。

原则上,C++也可能出现同样的情况,但由于C++ ABI(包括调用约定)必须更加复杂,编译器供应商并未就单一的ABI达成共识,但仍然可以通过回退到“extern “C””来实现互操作性。


5
这个陈述是正确的,但表达不够清晰。问题不是厂商之间无法就通用ABI达成一致(可以通过标准化委员会解决),而更多地是同一家厂商由于无法始终准确预测未来的发展而在不同时间无法达成一致。 - Patrick Fromberg

15

C的ABI是与平台相关的 - 它涵盖了诸如寄存器分配和调用约定等明显特定于特定处理器的问题。以下是一些示例:

x86有许多调用约定,在Windows下扩展声明使用哪一个。嵌入式Linux的平台ABI也随着时间的推移而改变,导致不兼容的用户空间。请参见此处 ARM Linux端口的一些历史记录,显示了转换到新ABI中的问题。


我已经去掉了损坏的链接。 - jasonleonhard

7
尽管已经尝试过在多个操作系统上为给定架构定义单个ABI(特别是在Unix系统上的i386),但这些努力并没有取得很大成功。相反,操作系统往往会定义自己的ABI...引用...Linux系统编程第4页。

6
即使对于C语言,ABI也有相当独立于平台的部分,取决于处理器(应该保存哪些寄存器,哪些用于传递参数等),以及取决于操作系统的部分(与处理器的因素基本相同,因为某些选择不是由架构强制执行的,而是出于权衡的结果,此外一些操作系统具有语言无关的异常概念,因此任何语言的编译器都必须生成正确的内容来处理这些,线程的处理也可能对ABI产生影响——如果一个寄存器指向TLS,则不能将其用于所需的用途)。
理论上,每个编译器都可以有自己的ABI。但通常情况下,对于几个处理器/操作系统,ABI由操作系统厂商固定,他们通常还提供C编译器和使用该ABI的常用库,而竞争对手则希望兼容。(如果C不是主要编程语言,则某些操作系统可能会有例外情况)。
但是,操作系统供应商可能会因为某种原因而更改ABI(例如,新版本的处理器可能具有您想在ABI中使用的功能之一-例如,有些人要求x86_64的32位ABI,从而可以使用所有寄存器)。在迁移阶段中 - 这可能需要很长时间 - 您可能需要处理两个ABI。

5

C语言也不行,对吧?

在任何给定的平台上,C语言几乎都可以使用。如果缺少一种语言作为跨语言通信的共通语言,则将无法使用。
几乎可能指的是由C编译器供应商选择的特定体系结构默认值被用于其他语言中。因此,如果Keil的ARM C编译器将使用从左到右的小端参数排序和堆栈传递参数以及一些预定寄存器来返回值,则来自其他编译器的extern "C"将假定与该方案兼容。

虽然这样的协议可能被认为是ABI的一部分,但与JVM浏览器沙箱等托管执行上下文不同,它本身远非完整的标准ABI。


3
在 C89 标准之前,许多平台的 C 编译器使用基本相同的 ABI(应用二进制接口),除了数据大小的差异。对于栈向下增长的机器,调用函数的代码会按照从右到左的顺序将参数推入堆栈,然后调用函数(在此过程中推送返回地址)。被调用的函数会将其参数留在堆栈上,调用者可以随意调整堆栈指针以删除它们[或者,在某些体系结构上,可能会就地调整堆栈值]。虽然 <stdarg.h> 使得大多数程序不需要依赖这种约定,但它仍然被使用了很多年,因为它简单并且效果还不错。虽然没有“官方”文件将其确立为跨平台“标准”,但大多数针对栈向下增长的机器的编译器都是这样工作的,导致比今天存在更高水平的一致性。

所以,就像ABI一样,只是有足够的变化使得称其为ABI不合适? ;) - hmijail
1
@hmijail:几乎所有为这些机器编写C编译器的人都知道其他编译器的做法,并在没有理由做其他事情的情况下遵循他们的方法。许多“标准”并不是由标准机构创建的,而只是一个人做某事,其他人也这样做的过程中自然形成的。 - supercat
大多数CPU访问寄存器比堆栈更快,因此标准化调用约定需要在堆栈上传递所有参数并没有意义。任何偏好使用寄存器传递参数的方法显然高度依赖硬件,不能成为标准跨平台方法。 - Mikko Rantalainen

3

C语言没有标准的ABI。这可以很容易地说明所有使用的调用约定(cdecl、fastcall和stdcall)。每种约定都是不同的ABI。


3
没有标准的ABI,因为C语言一直以来都是关注最大运行性能的,而具有最高性能的ABI取决于底层硬件。因此,ABI可能仅使用堆栈或优先使用寄存器来传递函数调用参数和返回值,这取决于特定的硬件需求。
例如,即使是amd64(也称为x86-64),也有两种调用约定:Microsoft x64和System V AMD64 ABI。前者将前4个参数放入寄存器中,其余的放入堆栈中。后者将前6个参数放入寄存器中,其余的放入堆栈中。我不知道为什么Microsoft为amd64硬件创建了不兼容的调用约定。据我所知,Microsoft变体的性能略差,并且是后来创建的。
更多信息请参见https://en.wikipedia.org/wiki/X86_calling_conventions

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接