第20.1章:独立实现与宿主实现是什么
1. 定义
标准定义了两类实现。参考文本是 cppreference:freestanding implementation,它只占一屏 —— 而这本身就是本章要讲的问题的一部分。按它陈述的顺序,它说的是:
- 你得到哪一类由实现定义。不是你能传的构建选项,也不是源文件的属性:由工具链安装决定。
-
__STDC_HOSTED__告诉你哪一类。宿主实现展开为1,独立实现展开为0。 - 在独立实现里,执行可以在没有操作系统的情况下发生。这一句就是全部理由,别的都是从它推出来的。
-
程序是否必须定义
main由实现定义。启动和终止也是:"启动包含执行命名空间作用域内具有静态存储期限的对象的构造函数"。内核入口就是一个启动过程只有几行汇编的独立程序。 - 程序能否拥有多个线程由实现定义。没有线程库、没有加锁、没有任何保证。
- 必须提供的头文件集合小得多,而且独立实现"拥有由实现定义的头文件集合",它"至少包含"清单上的那些。
这就是全部契约。注意其中没有什么:没有任何一句说哪些语言特性可用。按标准自己的措辞,独立实现仍然拥有异常、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 究竟改了什么
这个选项只改三件事,而且没有一件是库:
-
__STDC_HOSTED__变成0。检查它的代码从此走独立分支。 -
需要宿主 C 库的内建函数被关掉。
printf不再是会被展开成puts的内建函数,malloc不再被当作__builtin_malloc,main也不再特殊。 -
别的什么都没变。头文件搜索路径、库、启动文件、链接脚本都和之前一样。你仍然指向桌面的
/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 没有对应指令的算术运算会链接失败 |
每一行都是宿主工具链免费送给你的东西,而独立工具链把责任交还给你。这就是为什么本章不是一份编译选项清单:选项是最容易的那部分。