Ch20.1: What Freestanding and Hosted Implementations Are
1. The Definition
The standard defines two kinds of implementation. The reference text is cppreference: freestanding implementation, and it fits on one screen, which is itself part of the problem this chapter is about. Here is what it says, in the order it says it:
- Which kind you get is implementation-defined. Not a build flag you pass, not a property of the source file: the toolchain installation decides.
-
__STDC_HOSTED__tells you which. It expands to1for a hosted implementation and0for a freestanding one. - In a freestanding implementation, execution may happen without an operating system. That single sentence is the whole rationale, and everything else follows from it.
-
Whether a program must define
mainis implementation-defined. Start-up and termination are too: "start-up contains the execution of constructors for objects of namespace scope with static storage duration". A kernel entry point is a freestanding program whose start-up is a few assembly lines. - Whether a program can have more than one thread is implementation-defined. No thread library, no locking, no guarantee.
- The set of required headers is much smaller, and a freestanding implementation "has an implementation-defined set of headers" that "includes at least" the ones on the list.
That is the entire contract. Notice what is not in it: nothing about which
language features are available. As far as the standard's own wording goes, a freestanding
implementation still has exceptions, RTTI, new, thread_local and
floating point — it just has fewer headers. That gap between "fewer headers" and "the
target cannot do this at all" is where C++ freestanding lived for three decades, and it is
the subject of 20.3.
2. Asking the Compiler Which World You Are In
The macro is not a hint; the compiler defines it itself, from how it was configured. One program, two command lines:
#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
So -ffreestanding does flip the macro, and from the compiler's point of view
you are now in a freestanding translation unit. Keep that output in mind for section 4,
because the next thing that happens is that the program keeps working perfectly on your
desktop — and that is the trap.
3. What -ffreestanding Actually Changes
The flag changes exactly three things, and none of them is the library:
-
__STDC_HOSTED__becomes0. Code that checks it now takes its freestanding branch. -
Builtins that require a hosted C library are turned off.
printfis no longer a builtin to be expanded intoputs,mallocis no longer assumed to be__builtin_malloc, andmainis no longer special. -
Nothing else. The include paths, the library, the start files and the
linker script are the same ones as before. You are still pointed at your desktop
/usr/includeand your desktop libc.
This is the single most misunderstood flag in C++. -ffreestanding is a promise
from you to the compiler that you will not depend on the hosted runtime.
It is not a promise from the toolchain that a freestanding runtime exists. The toolchain
you have was built for your desktop, so it ships your desktop's headers, and the result is
a hybrid that is neither: a translation unit that believes it has no operating system,
compiled against a library that assumes one.
4. The Hybrid Breaks Immediately, and the Error Is Informative
Here is the same desktop toolchain with -ffreestanding, compiling a file that
includes <cmath>. libstdc++ guards that header:
$ 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 is libstdc++'s gate: it inspects
__STDC_HOSTED__ and refuses the header when it is 0. So the
desktop's standard library is not merely unsuitable for freestanding work, it
knows it is, and says so. The header list it accepts and refuses is measured in
20.6, and it is not the list the
standard requires.
5. What "Freestanding" Is a Property Of
The conclusion is the one that reorganises how you think about toolchains: freestanding is a property of an installation, not of a command line. To have one you need, at minimum:
| Piece | Who supplies it | What goes wrong without it |
|---|---|---|
| A compiler that targets your CPU without assuming an OS | A cross-build of the compiler (20.5) | Wrong ABI, wrong floating-point conventions, host headers found by accident |
| A C library, or none | newlib, picolibc, musl, or your own kernel's memcpy |
undefined reference to `memcpy' and friends |
| A C++ library subset | The standard's required list, or a vendor's extension, or nothing | fatal error: 'cstddef' file not found |
| Start-up code and a linker script | You, or your board support package | Execution begins at a random address with an uninitialised stack |
| The compiler runtime: division helpers, `memcpy`, soft float | libgcc, compiler-rt |
Link errors on arithmetic that the CPU has no instruction for |
Every row is a thing that a hosted toolchain gives you for free and a freestanding one makes you responsible for. That is why this chapter is not a list of compiler switches: the switches are the easy part.