第20.1章:独立实现与宿主实现是什么

English

1. 定义

标准定义了两类实现。参考文本是 cppreference:freestanding implementation,它只占一屏 —— 而这本身就是本章要讲的问题的一部分。按它陈述的顺序,它说的是:

这就是全部契约。注意其中没有什么:没有任何一句说哪些语言特性可用。按标准自己的措辞,独立实现仍然拥有异常、RTTI、new、thread_local 和浮点 —— 它只是头文件更少。而"头文件更少"与"目标根本做不到这件事"之间的这道缝,就是 C++ 独立实现活过三十年的地方,也是 20.3 的主题。

2. 问编译器你处在哪个世界里

这个宏不是提示;是编译器自己根据它的配置定义出来的。同一个程序,两条命令行:

#include <cstdio>

int main() { ::std::printf("%d\n", __STDC_HOSTED__); }
$ g++ -std=c++26 -O2 hosted.cc -o h1 && ./h1
1
$ g++ -std=c++26 -ffreestanding -O2 hosted.cc -o h2 && ./h2
0

所以 -ffreestanding 确实翻了这个宏,从编译器的角度看你现在处在一个独立的翻译单元里。记住这段输出,因为接下来会发生的事是:程序在你的桌面上依然完美工作 —— 而这正是那个陷阱。

3. -ffreestanding 究竟改了什么

这个选项只改三件事,而且没有一件是库:

  1. __STDC_HOSTED__ 变成 0。检查它的代码从此走独立分支。
  2. 需要宿主 C 库的内建函数被关掉。printf 不再是会被展开成 puts 的内建函数,malloc 不再被当作 __builtin_malloc,main 也不再特殊。
  3. 别的什么都没变。头文件搜索路径、库、启动文件、链接脚本都和之前一样。你仍然指向桌面的 /usr/include 和桌面的 libc。

这是 C++ 里被误解得最厉害的选项。-ffreestanding 是你给编译器的一个承诺:你不会依赖宿主运行时。它不是工具链给你的承诺:独立运行时的确存在。你手上这套工具链是为你的桌面构建的,所以它带的是桌面的头文件,结果是一个既不是这也不是那的杂种:一个相信自己没有操作系统的翻译单元,却被一个假定有操作系统的库编译出来。

4. 这个杂种立刻崩掉,而且报错很有说明性

下面是同一套桌面工具链加上 -ffreestanding,编译一个包含 <cmath> 的文件。libstdc++ 对这个头文件做了检查:

$ g++ -std=c++26 -ffreestanding -c t.cc
In file included from .../include/c++/17.0.0/cmath:46,
                 from .../include/c++/17.0.0/math.h:36,
                 from t.cc:1:
.../include/c++/17.0.0/bits/requires_hosted.h:34:4: error: #error "This header is not available in freestanding mode."
   34 | #  error "This header is not available in freestanding mode."
      |    ^~~~~

bits/requires_hosted.h 就是 libstdc++ 的闸门:它检查 __STDC_HOSTED__,为 0 时拒绝这个头文件。所以桌面的标准库不只是不适合独立开发,它知道自己不适合,并且说了出来。它接受和拒绝的头文件清单在 20.6 里实测过,而那并不是标准要求的那份清单。

5. "独立"是什么的属性

结论会重排你对工具链的看法:独立是一套安装的属性,而不是一条命令行的属性。要拥有它,你至少需要:

组件谁提供缺了会怎样
一个不以 OS 为前提针对你的 CPU 的编译器 编译器的交叉构建(20.5) 错误的 ABI、错误的浮点约定、意外找到宿主的头文件
一个 C 库,或者一个也没有 newlib、picolibc、musl,或者你自己内核里的 memcpy `memcpy` 之类的 undefined reference to 'memcpy'
一个 C++ 库子集 标准要求的清单,或者厂商的扩展,或者什么都没有 fatal error: 'cstddef' file not found
启动代码与链接脚本 你,或者你的板级支持包 执行从一个随机地址开始,栈未初始化
编译器运行时:除法辅助函数、`memcpy`、软浮点 libgcc、compiler-rt CPU 没有对应指令的算术运算会链接失败

每一行都是宿主工具链免费送给你的东西,而独立工具链把责任交还给你。这就是为什么本章不是一份编译选项清单:选项是最容易的那部分。