第20.2章:C 独立实现与 C++ 独立实现
1. C 在 1989 年就把独立实现做成了可用的东西
C 在标准化几乎其他任何东西之前就标准化了独立实现,因为这门语言的用户当时就在写内核,而一份描述不了 Unix 的标准是没有用的。要求是四个头文件:
<float.h> <limits.h> <stdarg.h> <stddef.h>
这就是一个符合标准的 C 独立实现必须拥有的全部库,而且从 1989 年至今没有变过。仔细看这份清单,因为它不是按口味选的子集:每一项都是一个编译器必须知道其内容的头文件。NULL 与 size_t、每种整数类型的取值范围、double 的表示,以及可变参数列表如何遍历。没有一项能用普通源码写出来,所以也没有一项能在不使语言不可实现的前提下被删除。其余的一切 —— printf、malloc、FILE —— 都是宿主的,独立 C 程序就是没有它们,而所有人都觉得这可以接受,因为内核本来也不会去调 printf。
还要注意 C 独立实现没有强加给你什么:这门语言没有异常、没有 RTTI、没有 new、没有线程库、没有隐藏的分配。"独立实现必须提供的库"和"独立实现能够实现的库"之间没有任何差距,因为那份清单是从编译器自身的需要里划出来的,没有别的东西。
2. C++ 把独立实现变成了一个承诺而不是一份清单
C++03 确实有一节讲独立实现。它要求 C 的头文件,加上语言自身特性所需要的 <new>、<typeinfo> 和 <exception> 中的一部分 —— 而错误就在这里。这些头文件之所以存在,是因为 C++ 有 new、RTTI 和异常,而这些特性需要一个运行时:一个堆、类型信息对象、一个 unwinder。于是 C++ 独立实现要求了需要运行时的头文件,而独立目标没有那个运行时。标准描述了一个它实现不了的库。
后面的一切都是由此推出的。一个符合标准的独立 C++ 实现,得为一块只有 8 KiB 内存的板子提供 `__cxa_allocate_exception` 和一个 personality routine。没有厂商这么干,所以每一个真实的独立 C++ 构建早已是"不符合标准"的,而标准自己的文字描述了一门没人编译的语言。
| C | C++ | |
|---|---|---|
| Freestanding specified since | 自何时起规定了独立实现 | 1989 年,此后从未改变 |
| 1998 年,且如所写无法实现 | Four, all compiler-owned | 必需的头文件 |
| 四个,全由编译器掌管 | 很多,其中好几个需要目标没有的运行时 | Exceptions, RTTI, new, and their support library |
| 语言强加给你的特性 | 除算术和函数调用之外没有 | 异常、RTTI、new,以及它们的支撑库 |
| Gap between the standard and reality | 内核要合规得怎么做 | 照常编译 |
把这些特性关掉,然后变得不符合标准
3. The Paper That Named the Problem
标准与现实之间的差距
没有
"用 -fno-exceptions -fno-rtti -nostdlib,然后祈祷"
这就是为什么遗留异常那一章不是口味问题。在独立目标上,选项从来不是"异常还是错误码",而是"异常还是一份符合标准的构建"。
3. 给这个问题命名的论文
P1105R0《Leaving no room for a lower-level language: A C++ Subset》 在引言里说出了内核和嵌入式领域的每个人二十年前就知道的事: