Ch20.2: C Freestanding vs C++ Freestanding

中文(简体)

1. C Made Freestanding Work, in 1989

C standardised freestanding before it standardised much of anything else, because the language's own users were writing kernels, and a standard that could not describe Unix would have been useless. The requirement is four headers:

<float.h>   <limits.h>   <stdarg.h>   <stddef.h>

That is the entire library a conforming C freestanding implementation must have, and it has not changed since 1989. Read that list carefully, because it is not a subset chosen by taste: each entry is a header whose contents the compiler must know. NULL and size_t, the range of every integer type, the representation of double, and how a variadic argument list is walked. None of them can be written as ordinary source, so none of them can be left out without making the language unimplementable. Everything else — printf, malloc, FILE — is hosted, and a freestanding C program simply does not have it, which everybody finds acceptable because a kernel was never going to call printf.

Notice also what C freestanding does not force on you: the language has no exceptions, no RTTI, no new, no thread library, no hidden allocation. There is no gap between "the library a freestanding implementation must provide" and "the library a freestanding implementation can implement", because the list was drawn from what the compiler itself needs and nothing else.

2. C++ Made Freestanding a Promise Instead of a List

C++03 did have a freestanding section. It required the C headers plus the parts of <new>, <typeinfo> and <exception> that the language's own features need — and that is where it went wrong. Those headers exist because C++ has new, RTTI and exceptions, and those features need a runtime: a heap, type information objects, and an unwinder. So C++ freestanding required headers that require a runtime that a freestanding target does not have. The standard had described a library it could not implement.

Everything downstream follows. A conforming freestanding C++ implementation would have to ship `__cxa_allocate_exception` and a personality routine for a board that has 8 KiB of RAM. No vendor did that, so every real freestanding C++ build was already non-conforming, and the standard's own words described a language nobody compiled.

CC++
Freestanding specified since 1989, and unchanged since 1998, and unimplementable as written
Required headers Four, all compiler-owned Many, several of which need a runtime the target lacks
Features the language forces on you None beyond arithmetic and function calls Exceptions, RTTI, new, and their support library
What a kernel does to comply Compile normally Switch the features off and become non-conforming
Gap between the standard and reality None "Use -fno-exceptions -fno-rtti -nostdlib and hope"

This is why the chapter on legacy exceptions was not a matter of taste. On a freestanding target the choices were never "exceptions or error codes"; they were "exceptions or a conforming build".

3. The Paper That Named the Problem

P1105R0, Leaving no room for a lower-level language: A C++ Subset says in its introduction what everybody in kernel and embedded work had known for twenty years:

Conforming C++ toolchains are ill-suited to target kernel and embedded domains. In practice, kernel and embedded developers almost always use compiler switches that make the toolchain non-conforming. This means that conforming C++ has left room for a lower-level language.

Read that last sentence twice. It is not a complaint that C++ is inconvenient; it is the observation that C++'s own freestanding mode was so unusable that a competing language — the C you can actually compile for a board — was left with a defensible niche. The paper's remedy is the one this book has been using all along: make the OS-dependent features optional as explicit, per-feature switches, so that a build can say "no exceptions, no RTTI, no default heap, no thread-local storage" in a way that is specified instead of accidental. Sections 20.7 to 20.12 walk through that list one feature at a time.

4. The Part C++26 Still Has Not Fixed

The standard's freestanding header list grew in C++23 and was scheduled to grow again in C++26 — and C++26 was not published. So the practical situation today is a three-way mess: some headers are required, some are vendor extensions that happen to work, and some are required only by a standard that nobody can comply with. That is the table in 20.6, and `std::array` is its most embarrassing entry.

Meanwhile the C list has not moved in thirty-five years, and does not need to, because it was drawn from the compiler's own requirements rather than from the library's ambitions. That is the difference between a design and an accumulation.