Ch20.9: Legacy Exceptions Cannot Exist Here

中文(简体)

1. The Feature

P1105R0 §4.1 is the section that started the modern freestanding work, and its first sentence is the argument: kernel and embedded environments cannot universally afford exceptions, and throwing one "requires a heap allocation on the Itanium ABI, and a large stack allocation on the Microsoft ABI, neither of which are suitable" in a kernel.

19.3 taught the mechanism and 19.8 measured it. This page is the freestanding question only: what does it need from the environment, and what do you do when that environment is the kernel itself?

2. Measured: Eleven Symbols

#include <stdexcept>

void fail(int x) noexcept(false) { if (x < 0) { throw ::std::runtime_error("negative"); } }

int main() { try { fail(-1); } catch (::std::runtime_error const &) { return 0; } return 1; }
$ g++ -std=c++26 -O2 -c legacy.cc && nm -u legacy.o
                 U _Unwind_Resume
                 U _ZNSt12__cow_stringC2EPKc
                 U _ZNSt13runtime_errorD1Ev
                 U _ZTISt13runtime_error
                 U _ZTVSt13runtime_error
                 U __cxa_allocate_exception
                 U __cxa_begin_catch
                 U __cxa_end_catch
                 U __cxa_free_exception
                 U __cxa_throw
                 U __gxx_personality_v0

Eleven, and every one of them is a component of an operating system facility:

There is no subset of that you can provide yourself. Writing them means writing a runtime library, compiled for your target, ABI-compatible with the compiler's expectations, and then maintaining it against every compiler release. This is the load that 20.3 described as making C++03's freestanding mode a lie.

3. In a Kernel There Is Nobody to Ask

On a hosted system the unwinder comes from libgcc_s.so, the frame tables are registered by ld.so, and the heap is brk/mmap. A kernel is the layer those would have been built on. If it wants legacy exceptions it must implement an unwinder over its own interrupt frames, an exception-table registry for its own loadable modules, and a heap that is reentrant enough to be called from a faulting context — and then it is maintaining a private C++ runtime inside the kernel, which is the definition of the problem.

There is a second reason that has nothing to do with machinery: an exception thrown in kernel context resumes in some frame. Which frame? A process that has already been killed? An interrupt handler with a clobbered register state? Legacy exceptions assume the unwound-to frame is a function call in a healthy thread, and in a kernel that assumption is simply false. This is what "especially broken for kernels" means (20.4).

4. On a GPU There Is No Version of It at All

The kernel at least could build the runtime at enormous cost. Device code cannot: every GPU programming model requires exceptions to be off, there is no unwinder in the device runtime, no dynamic loader, no exception tables, and no place to store a thrown object — a thread that throws has nowhere to go, because the other threads in the warp and block are not part of its call stack.

EnvironmentWhat legacy exceptions needWhat exists there
Freestanding / bare metal Unwinder, frame tables, heap, RTTI, loader Nothing unless you bring it
Kernel All of the above, plus a definable resume frame The kernel itself, which cannot assume any of it
GPU device code All of the above Not part of the model at all; exceptions are off by requirement
WebAssembly Unwinding as a target feature Not in the base target; proposals add it to the engine, not to your binary
Any -fno-exceptions build — error: exception handling disabled, use '-fexceptions' to enable

Five rows, and in four of them the answer is "this mechanism is unavailable". A language whose only error channel is this one is a language that cannot report an error on four of those targets — and the workaround, an error code threaded by hand through every function, is the thing C++ users were told they had escaped.

5. So They Are Banned, Not Discouraged

This is why 19.7 does not say "prefer herbceptions"; it says add -fno-exceptions to the build so that a legacy throw is a compile error. On a freestanding target, a build that has not banned them is a build that links on your desktop and fails on the board, which is the worst of both worlds.

The next page is the other half: the mechanism that does work here, with one linker symbol instead of eleven.