三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

拯救 C 语言 ABI:透明别名能否解决兼容性难题?

拯救 C 语言 ABI:透明别名能否解决兼容性难题?

1. 拯救 C 语言,为何先拯救 ABI?

2022 年 3 月 13 日,作者上一篇关于应用二进制接口(ABI)稳定性的文章发表后,此次文章标题令人意外,它与 Jason Turner 在 C++ Weekly 上“打破 ABI 以拯救 C++”的主题相悖。Jason 的观点正确,作者上一篇文章也展示了实现者反向修改标准库及以 ABI 威胁新特性的情况。但作者却声称要拯救 ABI,这引发疑问:难道 ABI 不该被摧毁?作者是否被收买或疯了?实际上,作者头脑清醒,未被收买。作者意识到,即便鼓吹更好的标准库、编写高性能代码,系统也不会因部分人而改变,但这并不意味着要放弃,因为打破 ABI 的方法不止一种。

2. 可怕的 ABI 是什么?

应用二进制接口(ABI)是编译器对计算机硬件资源逐位表示和使用方式的假设。在 C 或 C++ 代码中创建结构体或编写函数时,就相当于签署了无形契约。其涵盖 `struct`/`class` 成员位置等多方面。本文主要关注 C 语言,其 ABI 重要的是 `struct` 成员位置和函数参数、返回类型。由于 C++ 纳入 C 标准库,C 语言的 ABI 问题也成了 C++ 的问题,且 C 语言决策影响所有人。

3. C 语言的 ABI 如何体现?

C 语言的 ABI 是函数与二进制文件中生成的符号基本一一对应。例如声明 `do_stuff` 函数,不同参数类型生成的汇编代码不同。将参数从 `long long` 改为 `__int128_t` 后,汇编代码有明显变化,这表明编译器与编写函数定义的人达成了“契约”,这就是 ABI。C++ 有“名称修饰”,可避免符号混淆,而 C 语言几乎没有实现采用。C 语言编译代码的 `call` 指令只包含函数名,这导致其符号不稳定。

4. 从另一个角度看 ABI 会怎样?

实现 `do_stuff` 函数,`long long` 类型和 `__int128_t` 类型版本的代码和汇编差异大。若应用程序错误链接到不同版本,可能会出问题。

5. 故意制造问题会如何?

通过两个 `.c` 文件,一个声明 `long long` 类型的 `do_stuff` 函数,一个定义 `__int128_t` 类型的 `do_stuff` 函数,编译时链接器不在意类型不匹配,但运行时可能出现访问冲突等未定义行为。即便函数放在头文件中,问题仍可能出现。

6. 意外引发的 ABI 问题有哪些?

ABI 问题可能在编译器前端和链接器不报错时出现,库是新的问题来源。C 语言不进行名称修饰,共享库时需大家就符号达成一致。在 *nix 系统中,动态库更新需保持向后兼容性。小示例在调试模式有错误弹窗,但关键系统组件发布版本可能出现段错误,问题根源和症状难关联。使用错误头文件编译,编译器默认同名东西按预期运行,会导致很多问题,还会延迟特性采用,通常延迟 10 到 11 年,如 C99 引入特性到 C11 变为可选特性、Python 2.x 到 Python 3 的过渡、C++11 中 `std::string` 写时复制机制的改变。

7. 这还算好的结果吗?

C++ 情况如此,C 语言标准委员会若无法解决兼容性问题可能更糟。如 `realloc` 函数处理大小为 0 的内存块问题,DR 400 未完全解决,最终 N2464 被纳入 C23 标准。这表明标准由实现者决定,实现者不满则改变无法通过。

8. 但……为什么是 ABI?

这和 Victor Zverovich 的 fmtlib 在 C++20 到 C++23 期间情况类似,以不改变行为为由不遵循标准是强大武器。标准委员会通常不做成本高或实现者不想做的改变,陷入两难局面,很多讨论都会遇到类似情况。不过,某些平台有解决办法。

9. 一个古老的解决方案是什么?

为系统发行版设计的健壮 C 库使用汇编标签技术,NetBSD 已用超过 25 年。该技术可在不改变顶层代码时重命名最终二进制文件中的符号,很多 C 实现有不同方式实现,目标都是将 `NormalName` 替换为 `_BinarySymbolName`,musl libc 就用此技术实现兼容性。那么,为何不用于解决 ABI 问题呢?

10. 透明别名能解决问题吗?

论文 [N2901] 讨论了提供最终二进制文件中正式符号的方法,目标是使用成本为零、不使用无额外成本、不引入新函数或符号。该功能规范类似“函数的 `typedef`”,多个平台有相关实践,能在 Clang 中实现。示例代码显示,无论是否优化,生成的汇编代码都未提及别名。透明别名能在不被 ABI 锁定旧行为束缚的情况下,让新代码使用相同名称访问新行为和语义。用户可根据宏定义选择不同接口和实现,新编译代码可升级而不影响旧使用者。但因没有共享认可的解决方案,拒绝实现的实现者可拒绝改进,影响生态系统。`intmax_t` 相关问题受 ABI 影响难解决。那么,这个解决方案能解决问题吗?在 [Godbolt.org] 上有实现该功能的编译器,设置完整测试,使用修改构建 Clang 编译器,能解决主流平台的 ABI 问题吗?

11. ABI 测试:`maxabs`

文章未完整阐述此部分内容。

← 返回列表