Martin Uecker stood up at Kernel Recipes 2026 and defended C, then immediately admitted the language’s biggest liability. The liability is undefined behavior, and the committee that owns the standard is now actively deleting it. Jonathan Corbet’s writeup of the talk, Reducing undefined behavior in the C language, is worth your time if you maintain anything written in C.

Uecker’s day job is biomedical engineering: he writes free software that controls MRI scanners. That background explains his framing. When your code drives a machine that pushes magnetic fields at a person, “the compiler deleted my overflow check” is not a compiler theory argument, it is a patient safety problem. He still called C a great language, pointing at portability, decades of stability, fast compilation, and the property that what you write is roughly what executes. Then he got to the part where that contract breaks.

How the standard creates the problem

The C standard defines semantics through an abstract machine, and only observable behavior has to match what that machine would do. Anything the standard leaves undefined, the optimizer may treat as impossible. That is where the demons live. The classic example is hoisting a check: you write code that guards against a division by zero, the compiler sees a division in a path you proved unreachable, decides undefined behavior cannot happen there, and deletes your guard. Your safety check was the thing that made the code undefined. The compiler optimized away the proof.

By Uecker’s count the standard carries around 100 instances of undefined behavior. The in-progress C2y draft has already removed 45 of them, reclassifying some as constraint violations (which produce a diagnostic) or as “extensible behavior” that implementations must handle sanely. The working group documents this in WG14 paper N3957, and it tracks as real progress, not rewording for the sake of rewording.

The committee is not working alone. Three study groups cover the memory object model, memory safety, and undefined behavior, and recent standards have already moved. C23 removed K&R function definitions, trigraphs, and support for exotic signed integer representations, while adding bit-precise integer types and checked integer operations. It also added what the talk called a “no time travel” rule: a compiler may not hoist an operation above a volatile store in a way that retroactively changes observable behavior. C++ solves the same problem differently, with an explicit std::observable_checkpoint() call, which is exactly as fun as it sounds when you have to sprinkle them by hand.

Coming in C2y

The next standard continues the cleanup and adds practical features: case ranges, named for loops, and a _Countof() mechanism for determining array lengths. None of these sound dramatic until you notice what they replace. Case ranges and _Countof() exist because the alternatives, chains of comparisons and hand-maintained length constants, are where off-by-one errors and out-of-bounds reads are born. The standard is slowly making the safe spelling the easy spelling.

The performance excuse is weaker than you think

The standing defense of aggressive UB exploitation is performance: compilers squeeze out speed precisely because they may assume undefined paths never happen. A 2025 ACM study put that under measurement and found the payoff is minimal for the LLVM benchmarks and UB categories evaluated, with most regressions recoverable through better optimization algorithms or link-time optimization. In other words, the price of defined behavior is far lower than the folklore claims. That finding reframes the whole debate: if exploiting UB buys almost nothing, keeping UB in the standard buys even less.

Memory safety: the part that will not go quietly

Uecker split memory safety into three sub-problems: type safety, spatial safety, and temporal safety. C is making real progress on the first two, largely because tools and standard features keep improving. Temporal safety, meaning use-after-free and friends, is the hard one. Rust has an inherent advantage here. For C projects that cannot rewrite, the talk pointed at CHERI hardware capability machines and the Fil-C project as the most promising near-term ways to catch temporal bugs in existing code.

Until then, the practical defense is layered: crank compiler warnings, run static analyzers, and enable sanitizers in CI. UBSan in trapping mode is worth special attention because it converts silent undefined behavior into loud crashes, which is exactly what you want in a build gate even if you never ship with it.

What this means for your codebase

If you maintain C, three moves are worth making this month. Audit for the recurring UB categories: division by zero, uninitialized reads, comparisons between unrelated pointers, reads of struct padding. Replace each with a defined alternative, and treat “it works today” as a coincidence, not a property. Wire UBSan and a static analyzer into CI so new UB fails a build. And watch C2y. If your codebase is large and long-lived, the difference between the C you wrote against and the C the committee is standardizing will keep growing, and the projects that track the standard will spend far less on remediation than the ones that get surprised by it.

The deeper shift is cultural. For twenty years the community treated undefined behavior as an unfortunate fact of life, a tax the compiler collected in exchange for speed. The committee is now saying out loud that the tax is mostly not buying anything, and deleting it line by line. That is the most optimistic thing to happen to C in a decade.

Leave a Reply

Your email address will not be published. Required fields are marked *