TL;DR: It's golden for Static Analysis.
A dreadful example
One of the issues with defined behavior, is that readers can no longer distinguish between accidents and intent.
For example, in C, unsigned integers are defined to wrap on overflow. This is useful in a number of cases: when computing a hash (just add) or when "temporarily" overflowing (a - b + c = a + c - b). But there's also plenty of cases where overflows are just errors, some of them leading to catastrophic failures (malloc(n_items * sizeof *items)
being particular bad => use calloc
instead).
The problem, though, is that because it has been defined, a reader cannot know, for certain, whether the overflow is intended (and fine) or accidental. And any static analysis tool (compiler, linter, ...) warning about every overflow will get thrown out of the window because it will have way too many false positives.
Reading from uninitialized memory
In general, reading from uninitialized memory is most likely to be a bug, an accident, and therefore it is good that it be spotted by readers, and detected by tools, as the accident that it is.
Making it UB is one such way to ensure that no reader, nor tool, will ever have the slightest doubt that it could potentially be intended, instead of the accident that it is.
Alternative: Debug vs Release
The Rust language offers a more interesting alternative.
In its quest to eliminate UB, the Rust language was confronted to the integer overflow issue. Unfortunately, trapping on overflow results in significant penalties even in optimized binaries, not only because LLVM only includes the detection for debugging purposes, but also because trapping on overflow breaks fundamental properties -- such as associativity of addition or multiplication -- which in turn prevent many optimizations. In fact, even auto-vectorization is impossible in many cases, because vector instructions only offer wrap-on-overflow behavior :(
Faced with UB on overflow vs Wrap on overflow... the Rust language toyed with the idea of yielding an unspecified value on overflow, and finally settled on:
- Wrap on overflow, in Release, by default.
- Panic on overflow, in Debug, by default.
(Plus some verbiage that panicking may become the default behavior in Release at a later point, as compilers evolve, and a flag to override the defaults)
The exact options selected matter less, for the purpose of generalizing the rule, than the divergence between Debug and Release behavior. This divergence of behavior essentially codifies that despite being specified, overflow is necessarily an accident.
How to apply this divergent behavior to reads from uninitialized memory is left as an exercise to the reader.
malloc
andrealloc
can be read from without being initialized without causing undefined behavior as long as you are using a type with no trap representations. $\endgroup$