Ch20.4: Why Linus Torvalds Called It a "BLOODY STUPID IDEA"
1. The Message
This is the most-quoted message in the history of C++ arguments, and it is usually quoted without the two pages that precede this one. Here it is in full, because the specific claims matter more than the tone:
From: Linus Torvalds
Subject: Re: Compiling C++ kernel module + Makefile
Date: Mon, 19 Jan 2004 22:46:23 -0800 (PST)
On Tue, 20 Jan 2004, Robin Rosenberg wrote:
>
> This is the "We've always used COBOL^H^H^H^H" argument.
In fact, in Linux we did try C++ once already, back in 1992.
It sucks. Trust me - writing kernel code in C++ is a BLOODY STUPID IDEA.
The fact is, C++ compilers are not trustworthy. They were even worse in
1992, but some fundamental facts haven't changed:
- the whole C++ exception handling thing is fundamentally broken. It's
_especially_ broken for kernels.
- any compiler or language that likes to hide things like memory
allocations behind your back just isn't a good choice for a kernel.
- you can write object-oriented code (useful for filesystems etc) in C,
_without_ the crap that is C++.
In general, I'd say that anybody who designs his kernel modules for C++ is
either
(a) looking for problems
(b) a C++ bigot that can't see what he is writing is really just C anyway
(c) was given an assignment in CS class to do so.
Feel free to make up (d).
Linus
The message is from January 2004. It is twenty years old and it is still the standard opening move in every argument about C++ in kernels, which means it is worth checking rather than cheering or booing. So: three bullets, one at a time, against what this chapter has actually measured.
2. Bullet One: "The Whole C++ Exception Handling Thing Is Fundamentally Broken"
Correct, and especially for kernels, and this is the bullet that is not an opinion. 19.8 measured 934,540 ps per legacy throw against 885 ps per herbception, and 19.3 showed the mechanism. 20.9 of this chapter shows what it costs at link time: six runtime functions, an unwinder, an exception table per function, a personality routine, and RTTI.
"Especially broken for kernels" is exactly right and deserves unpacking, because there are three separate reasons and people usually name one:
- The failure path allocates. A kernel reporting out-of-memory by allocating memory is a bug in the reporting, not in the allocation.
- The failure path walks memory. Unbounded latency in a context that may be holding a spinlock or servicing an interrupt is not "slow", it is incorrect.
- The runtime may not exist. A kernel is the thing an unwinder would have to be built on. There is no lower layer to ask.
The mechanism this book uses answers all three, which is why the criticism survived the mechanism: herbceptions put the error in two registers on the return path (20.10), allocate nothing, and need one data symbol from the linker.
3. Bullet Two: "Compilers That Hide Memory Allocations Behind Your Back"
Also correct, and it is the bullet that has aged the best, because the list of hidden allocations got longer after 2004, not shorter:
-
newis one global heap with one failure policy, chosen for you at link time (20.7). -
Every legacy
throwallocates the exception object. -
::std::system_errorallocates twice before the throw even starts (19.5). -
std::string's message tables,what()strings andmessage()returns all allocate on the error path. - A container that needs to grow will call that global heap from inside a function you thought was simple.
The answer is not "never allocate", it is "make the allocation source a type, so
the caller can see it in the signature". That is what a fast_io allocator is
(19.9): the policy is
part of the container type, so a kernel function cannot accidentally allocate from a heap
it does not own. And a kernel needs several heaps — per zone, per NUMA node, per
device, DMA-safe or not — which is why a single ::operator new is not
merely hidden, it is insufficient.
4. Bullet Three: "You Can Write OO Code in C Without the Crap"
This is the bullet that reads as 1992, and it is the one place where the message is
answering a different question than the one you are asking. What Torvalds was rejecting
was C++ as it existed then: exceptions, RTTI, hidden allocation, and template
diagnostics that made a misplaced > produce four screens of noise. What he
was defending was the ability to write a filesystem with an operations table — which
is a vtable, spelled struct file_operations.
A modern freestanding C++ keeps the parts that structure code without a runtime:
constexpr (Ch7), templates
(Ch15), concepts, deterministic destruction
(Ch14), and now herbceptions. None of those need an
operating system. The complaint was never about the syntax; it was about what the
generated code would do when the target had nothing underneath it.
5. What Has and Has Not Changed Since 2004
| Claim | 2004 | Today |
|---|---|---|
| Legacy exceptions are broken for kernels | True | Still true, and still the mechanism C++ ships by default |
| Compilers hide allocations | True | Still true; ::operator new has no competitor in the standard |
| Freestanding C++ is a lie | True (C++03) | Partly fixed (C++23), then stalled: C++26 was not published, so
std::array is still not a freestanding header |
| C cannot express the abstractions without cost | True | False: constexpr, concepts and templates at zero run-time cost are
the one thing C never got |
| Error handling requires an operating system | True | False for herbceptions |
Two of the three bullets were about the mechanism, and both mechanisms have a replacement now. What has not changed is the default: a C++ build in 2026 still gives you legacy exceptions, RTTI and a global heap unless you turn them off. That is why this book turns them off in every example and says so in 19.7.