Everything in C is undefined behavior
A blog post arguing that “everything in C is undefined behavior” prompts a deep look at how fragile C and C++ programs can be when they rely on details the language standard leaves unspecified, from misaligned pointers and signed integer overflow to volatile access and function pointer casts. Commenters debate whether the real problem is the languages, modern compilers aggressively exploiting undefined behavior for optimization, or programmers treating implementation quirks as guarantees, and contrast this with safer or more modern alternatives like Rust, Zig, or heavy use of sanitizers and static analysis. There is broad agreement that completely avoiding undefined behavior in nontrivial C/C++ code is practically impossible, raising questions about portability, security, and how much longer these languages should be used for new systems.
Unaligned pointer casts and the C spec
- Big debate around
const int* magic_intp = (const int*)bytes;wherebytesisuint8_t*. - One side: C11/C23 6.3.2.3p7 says converting to a misaligned pointer is itself UB, even before dereference.
- Other side: casting pointers is allowed; only deref is UB. Compiler could legally “round” the pointer to a valid aligned value.
- Disagreement shows how even experienced programmers interpret the spec differently.
Hardware realities vs language guarantees
- Many note modern x86/ARM64 handle unaligned access fine or with small cost; historically some RISC and MMIO do not.
- Some argue C’s UB for unaligned access is obsolete and should be defined (e.g., “succeed or trap”).
- Others respond that C must remain portable to hardware that truly cannot support unaligned or atomic unaligned operations.
What UB means and how compilers use it
- Clarification: UB = “the standard imposes no requirements”; compilers are allowed to assume UB never happens and optimize accordingly.
- Examples: removing NULL checks, erasing code after (or even before) UB, assuming signed overflow doesn’t occur, treating dead branches as impossible.
- Debate whether compiler writers interpret “no requirements” too legalistically versus genuinely needing these freedoms for performance.
Prevalence and inevitability of UB
- Several commenters claim essentially all non-trivial C/C++ code contains UB; humans can’t avoid it completely.
- Others counter that rigorous style, careful arithmetic, and good tooling can keep real miscompilations rare, though it’s hard.
- Volatile, infinite loops, pointer arithmetic, signed overflow, character classification, and function pointers are common subtle sources.
Standards, tools, and mitigation
- Discussion of moving some UB to implementation-defined behavior, but not things like invalid pointer deref.
- C/C++ committees are slowly “slaying demons” (e.g., mandating two’s complement), but UB will remain central.
- Tools mentioned: UBSan, ASan, static analyzers, valgrind, specialized analyzers (e.g., Frama-C, IKOS); they catch many but not all issues.
Languages and alternatives
- Strong undercurrent comparing C/C++ to Rust, Zig, Java, Ada, etc.
- Some see C as dangerously outdated and favor safer languages; others value its simplicity, control, and performance, accepting UB as a tradeoff.
- “Just don’t write UB” is widely seen as unrealistic in large codebases, prompting interest in safer languages and better tooling (including, controversially, LLM-based assistants).