Falsehoods programmers believe about null pointers
Null pointers, offsets, and array indexing
- Several comments expand on the idea that a crash address near zero (e.g., 0x420) often indicates a null base pointer plus a field offset in a struct or class; the offset can help locate the offending field.
- There is debate over whether array indexing on a null pointer is meaningfully distinct: in C,
p[n]is*(p + n), so doing pointer arithmetic on a null pointer is already undefined behavior, even if the computed address is non-zero. - Some low-level patterns (like unions of pointer-or-error codes, or large structs/arrays starting at address 0) can yield non-obvious offsets that complicate diagnosing null-related crashes.
Undefined behavior and compiler behavior
- Multiple participants emphasize that dereferencing a null pointer is undefined behavior in C/C++, and that trying to be “clever” about what actually happens is dangerous.
- Historically, compilers mostly treated UB as “will probably just access address 0”, but modern optimizers aggressively exploit UB, leading to reordering, dead-code elimination, or “time travel”-like effects.
- There’s disagreement on how much programmers should care about the standard: some prioritize strict conformance to keep future compilers predictable; others focus on testing against specific compilers/platforms and writing straightforward code.
- Discussion dives into whether “undefined” means “defined somewhere else”; several commenters push back strongly, clarifying that UB is intentionally unspecified and may vary across compilers, versions, flags, and hardware.
C as a “high-level” language and alternatives
- Some argue C is more of a high-level “abstract machine” language than people admit, making it ill-suited as a simple “portable assembler.”
- Others note C++ is slowly fixing longstanding portability issues (e.g., mandating two’s-complement integers, proposed fixed 8-bit bytes) but becomes too complex for small systems.
- There’s a lively Rust/Zig vs C thread: some see Rust/Zig as serious modern replacements; others argue they’re unavailable or impractical on many targets, or unsuitable for certain low-level tasks (e.g., complex garbage collectors).
Signals/exceptions vs explicit null checks
- The article’s suggestion of “ask for forgiveness” (rely on faults/signals instead of null checks) is widely criticized for C/C++:
- SIGSEGV cannot reliably distinguish null dereferences from other memory faults.
- It depends on UB (null address unmapped), may create fragile signal handlers, and can interact badly with optimizations.
- Some note that, in managed languages with JITs, omitting explicit null checks and relying on hardware traps can be a real optimization on hot paths, but the thrown exceptions themselves are very slow.
- Others argue a single null check is trivial for CPUs and easily predicted, so the practical win is dubious without hard data.
Embedded, OS behavior, and hardware details
- Several comments highlight that address 0 may be valid on some embedded platforms (RAM, flash, vector tables), so using NULL as “always invalid” is dangerous there unless the MPU is configured to trap it.
- There’s clarification that in the C standard, a null pointer is guaranteed not to compare equal to any valid object/function pointer, even on systems where address 0 is usable memory.
- Claims that Windows “always” turns null dereferences into access violations are refuted with references to counterexamples and subtle behaviors.
Usefulness and genre of the article
- Some readers like the historical and low-level detail; others find it trivia-heavy and misleading for average programmers, who should simply treat null dereference as UB and never rely on observed behavior.
- There’s criticism that the piece misuses the “falsehoods programmers believe” genre, which traditionally targets real-world assumptions that break user-facing software (e.g., names, time zones), rather than niche language-lawyer corner cases.