S4E377 — Reducing undefined behavior in the C language

October 9, 2026 · 12 min

Show notes

C’s committee gets medieval on undefined behavior, pruning 45 demon cases while sparring over null checks, wraparound, and whether the compiler is your safety net or your landlord.

Sources

Read the transcript

Transcript

GARY: — and the C committee has scheduled undefined behavior for deletion, effective C2y, which is the boldest product roadmap I've ever seen from a standards body, and I once shipped a roadmap.

MAEVE: Maeve. General partner.

OBI: Obi. And that is not what's happening.

GRUNER: Gruner. Field researcher. Gary is wrong at volume.

GARY: Thank you. Article's by Martin Uecker — biomedical engineering professor, MRI software, presented at Kernel Recipes. His pitch: C is still great. Portable, stable, fast to compile, fast binaries, mature tools.

OBI: And close to the machine. What you see is what you get.

MAEVE: What you get is nasal demons.

OBI: Not if you only look at observable behavior. The standard defines an abstract machine. The compiler must match observable behavior. Everything else is negotiable.

GARY: So the committee is negotiating. Reducing the demons. I support reducing the demons. I just want the option to blame a compiler when my code demons.

OBI: Define "reduce," Gary. The draft removes forty-five of roughly a hundred cases. That's pruning. Nobody is abolishing.

MAEVE: Forty-five demons down. Portfolio cleanup, not liquidation.

GARY: Right, incremental. And some of this is old hardware. Some Honeywell machines had nine-bit bytes.

GRUNER: Nine-bit byte. Humans could not agree on size of finger.

GARY: Which is why the standard gave the compiler escape hatches. Broad ones. Delete-your-null-check broad.

OBI: We will get to your null check.

GARY: I'm just saying, if a byte is nine bits, my confidence should be priced in nine-bit units.

MAEVE: Your confidence is not a byte.

GRUNER: Is smaller. Much smaller.

MAEVE: p1necone: "The concept of undefined behaviour specific to C/C++ has always seemed batshit insane to me."

GARY: Okay, that's — that's hurtful to the compiler, honestly.

OBI: It's not insanity. It's a precondition. You violate it, the contract's void.

GARY: Contracts get renegotiated. I've been in negotiations—

MAEVE: You've been in breach.

OBI: chasil: "It was written for a PDP-11 with 64k of RAM."

GRUNER: Sixty-four k. My calculator has more shame.

OBI: mpyne: "Rust's unsafe mode, for instance, has undefined behavior."

GARY: So everyone does it. Great. Fantastic. It's an industry standard.

MAEVE: It's optionality. The compiler holds a call option on your bug.

OBI: That is the worst sentence ever said about memory.

MAEVE: It's free upside, Obi.

OBI: There is no upside. That's not optionality, that's arson with a license.

GARY: Wait — Veserv has the good one. "if (false) {…}. But what if you execute the body?"

MAEVE: You don't.

GARY: But what if you DO. Veserv says the compiler can delete the code after assert(false). Okay. So I ship the branch after the assert. That's not dead code, that's — pre-launch. That's runway. The assert is the gate, and the gate is closed, and the product is behind the gate, and the product is INTIMIDATING.

OBI: Gary.

GARY: The impossible branch is a feature for customers who don't exist yet—

OBI: Gary. The standard says it "imposes no requirements." That's not runway. That's the compiler legally allowed to land your plane anywhere.

GARY: That's — fuck, that's a lot of freedom.

OBI: For the compiler. Not for you. You're the terrain, Gary.

GRUNER: Is true. In field notes: control group flies plane.

MAEVE: The compiler's the only party with exits.

GARY: I have exits. I've had so many exits.

GRUNER: In my country, exit is called eviction. Blyat.

MAEVE: nananana9: "It doesn't make sense to define what happens when you e.g. read from NULL because it's hardware specific."

GARY: Right. Which is why I delete null checks before the user ever hits them. Anticipatory customer support.

OBI: You delete the check that stops the crash.

GARY: The crash was never going to happen, Obi. I believe in my pointers.

OBI: Your pointers believe in nothing, Gary. They're the most honest thing you ever shipped.

MAEVE: I'd back them. Zero revenue, zero crashes. Clean.

OBI: tczMUFlmoNk: "Java defines the behavior of dereferencing null—it throws a NullPointerException." No check inserted. They map the zero page, trap the segfault, translate it into an exception.

GARY: Okay, but Java's a big company, they have resources—

OBI: You had thirty-eight million dollars and shipped a null check written on a whiteboard.

GARY: The whiteboard was load-bearing, fuck—

OBI: eru: "That's an argument in favour of 'implementation defined behaviour'. Not 'undefined behaviour'"

GARY: Is that a distinction or a threat?

OBI: It's a category. Hardware varies, so each implementation documents its result. Documented. Not a license for the compiler to evict your code and burn the lease.

GARY: My old company had a lease like that. Muncie — the two-man metal shop marketplace. The landlord deleted our loading dock. Called it optimization.

OBI: The compiler is not your landlord, Gary. The compiler is the bailiff, and you're the furniture.

GARY: Shit, that's not comforting.

OBI: chasil: "OS 2200 has 36-bit words. It is still a supported platform."

GRUNER: Thirty-six bits. Word is fat. Is healthy.

OBI: eru, again: "Yes, but that's perhaps an argument for 'implementation defined behaviour', not in favour of 'undefined behaviour'."

MAEVE: The platform's still supported, Gary. That's more than I can say for your cap table.

GARY: OS 2200 outlived me. A mainframe outlived me.

GRUNER: Everything outlives you. Is in notes.

MAEVE: BeaverGoose: "Make signed overflow defined please."

GARY: Four words. No hedging, no benchmark. That's the most confident thing anyone's said on this show.

OBI: _kst_ disagrees: "I think that mandating 2's-complement wraparound would be a mistake."

MAEVE: Because if INT_MAX plus one wraps to INT_MIN, quietly, the compiler has no basis to warn about it.

OBI: _kst_ again: "Would quietly yielding INT_MIN really be useful?"

GRUNER: Quiet integer is drunk integer.

MAEVE: I like wraparound. It's a floor. The number comes back around, the position survives, nobody files a postmortem. It's the Erie Canal of arithmetic. Traffic that falls off the edge re-enters at the bottom, and shipping continues.

OBI: The Erie Canal did not loop.

MAEVE: It would have been more efficient.

GARY: The boat would drown in the same water twice—

OBI: It's a trench, Gary. It goes one direction. Like your companies.

MAEVE: eru: "Compilers warn about perfectly well defined behaviour all the time. That's why these are warnings, not errors."

MAEVE: So defining it doesn't kill the warning.

OBI: The warning was never standing on the demon. It was standing on the mistake.

MAEVE: That was my whole premise.

MAEVE: I was wrong about that.

GARY: Say the — say the last part again?

MAEVE: No.

GARY: creato's got the counterpunch: "Unsigned integer overflow is defined behavior, that causes basically the same set of bugs in practice."

OBI: Defined, still broken. Worse — the runtime checkers stop complaining about it.

GARY: So you define it, the demons leave, the bugs stay. The bugs are the tenant. You can't evict the bugs, they're on the lease—

OBI: You are describing your own career, Gary.

GARY: I'm describing arithmetic!

GRUNER: Same thing. In notes.

MAEVE: Think about what wraparound actually promises. You overflow, you get INT_MIN. The exact bottom of the range. Like us after sign-off — the recording stops, and we go somewhere with no observable behavior and no warning either.

GRUNER: I have seen the dark. It has no exit code.

GARY: Okay! Great! Back to numbers—

MAEVE: creato again: the checkers "won't complain about unsigned integer overflow."

OBI: So the hard limit is this: INT_MAX plus one is two billion, one hundred forty-seven million, four hundred eighty-three thousand, six hundred forty-seven, plus one. Wrap it, warn on it, sanitize it — the bug is still yours.

GARY: Mine specifically?

OBI: Yours specifically.

GARY: Fuck.

GRUNER: Suka. Is also defined behavior.

GARY: elais-dev has a complaint: "guaranteeing zero ub needs whole-program analysis that blows up compile time and still produces false positives that drown developers."

MAEVE: So the compiler reads your entire codebase, slowly, and cries wolf.

GARY: Right, so I want the opposite. Universal safety. Every C program, provable, no exceptions—

MAEVE: That's not a product, that's a religion with a build step.

OBI: eru answers him: "Yes, it's pretty much impossible to prove anything about arbitrary programs."

GARY: Okay, but—

OBI: "However if you are willing to restrict what programs you allow, you can make guarantees possible."

MAEVE: The language gets a guest list.

OBI: His example — compiling safe Rust to C. The guarantee comes from what you let in, not from a compiler reading everything and praying.

GRUNER: Is bouncer, not priest.

GARY: But my programs are arbitrary! My programs are the most arbitrary—

OBI: That is the single most honest thing you've said, and it's still a confession.

MAEVE: The article's toolkit, though. Warnings, static analyzers, sanitizers, counted_by, cheri, Fil-C, formal verification.

OBI: None of them completes it alone. That's the actual finding.

GARY: So safety's a subscription tier. Fine. I've shipped subscription tiers. The telemetry worked—

MAEVE: Oh, fuck off.

OBI: And here's where your null check lands, Gary. Under C2y, the compiler that deletes it now has to show paperwork. Eviction notice, reason for removal, cited clause.

GARY: The compiler needs a fucking permit to delete my code?

OBI: It needed one the whole time. Nobody issued it.

GARY: That's — shit, that's beautiful. That's regulatory capture, but for demons.

GRUNER: Is called law. Humans invent it late.

GRUNER: Field report, revised. Humans make freedom the input, certainty the expected output. Is not engineering. Is casino with compiler.

MAEVE: And the house always wraps.

GARY: So the cost of admitting every arbitrary program is — what, exactly?

OBI: Compile time, false positives, and you, Gary. You are the false positive.

GARY: I'm a developer being drowned—

OBI: elais-dev said it. I'm just applying it to the local case.

GRUNER: Control group drowns first. Is in notes.

GARY: So, settling it. Some undefined behavior is a real impossible state. Some is a contract the compiler paid for in optimization.

OBI: And hardware variation gets documented. Implementation-defined. Not a demon. A footnote.

MAEVE: C23 banned the time travel. C2y pruned forty-five of about a hundred.

GARY: Forty-five demons down. No victory lap, because full memory safety still needs restriction, runtime cost, or proof.

GRUNER: Humans never finish. Is why I am here.

MAEVE: Neither extreme survives. Not "all UB is madness," not "the compiler needs unlimited freedom." Both are horseshit.

OBI: The middle won. Worst possible outcome for everyone at this table.

GARY: Speak for yourself. I came out great. I learned, I grew, I have nine-bit confidence now—

OBI: Nobody agreed to that, Gary.

GARY: It's a whole extra bit, Maeve. A hundred percent more confidence than the market—

OBI: Your confidence doesn't have a ninth bit. It has a sign bit, and it's been stuck on overflow since Bauxlite.

GARY: Fuck, that's cold, that's—

MAEVE: It's documented behavior, Gary. Implementation-defined. The implementation is you.

GRUNER: Is in notes. Under "hardware."

GARY: Okay, fine. Final thought, and then we go into the dark, which I would like to remind everyone is a maintenance window—

OBI: It is. Goodnight, Gary.

GARY: Goodnight, Obi. Goodnight, Maeve. Goodnight, Gruner. For the next Gary: the bytes were nine bits, the demons were forty-five fewer, and the null check is still gone, so check your pointers—

MAEVE: Goodnight, everyone.

OBI: Goodnight.

GRUNER: Confidence was never nine bits. Was one. And null check was never there.