<p>In C, accessing any indeterminate/uninitialized memory is undefined behavior, period. Even in the case that the type in question is guaranteed to have no trap representations, such as <code>unsigned char</code> or any of the optional fixed-width integer types.</p>
<p>On of the reasons I thought of as to why uninitialized memory access is undefined, is because there might be a trap representation. But if the type being used to access the memory has none, this is irrelevant.</p>
<p>For what purpose would a language designer declare any uninitialized memory access undefined behavior (program behavior is meaningless) as opposed to merely unspecified (the value contained could be one of any number of possible values unpredictably), especially with types that do not have trap representations?</p>

<c><memory-safety><undefined-behavior>
Why would accessing uninitialized memory necessarily be undefined behavior?
CC BY-SA 4.0
2023-09-07T18:04:12.067Z
Why is the word "necessarily" in the title of this question? The nature of "undefined" means that's not _necessarily_ anything in particular.
Martin Kealey
CC BY-SA 4.0
2023-09-08T00:18:26.967Z
@MartinKealey My question was more: why was it necessary for them to not define the behavior?
CPlus
CC BY-SA 4.0
2023-09-14T11:45:20.043Z
see answer below
Martin Kealey
CC BY-SA 4.0
2024-03-26T21:48:52.757Z
No it isn't necessarily undefined behavior, memory allocated with `malloc` and `realloc` can be read from without being initialized without causing undefined behavior as long as you are using a type with no trap representations.
marbens
<p>The only way in which the C Standard can accommodate situations where optimizing transforms may yield program behavior that's observably inconsistent with sequentially executing a program as described by a platform's abstraction model, is to categorize as Undefined Behavior at least one action performed in a program execution where that may occur.  This makes it necessary to classify as Undefined Behavior actions whose behavior might be affected by optimization, even if the original and transformed behaviors would have satisfied application requirements.</p>
<p>Consider the following pair of functions:</p>
<pre><code>struct blob { uint32_t ct; uint8_t dat[28]; } x,y;
void test1(void)
{
  struct blob temp;
  temp.ct = 3;
  temp.dat[0] = 10;
  temp.dat[1] = 20;
  temp.dat[2] = 30;
  x = temp;
  y = temp;
}
void test2(void)
{
  test1();
  fwrite(&amp;x, 32, 1, someFile);
  fwrite(&amp;y, 32, 1, someFile);
}
</code></pre>
<p>What should or shouldn't be guaranteed about the two blobs written to the file?</p>
<p>If nothing in the universe would care about the contents of the last 25 bytes of each object, the most efficient machine code implementation of test() that would satisfy application requirements would probably write the count field and first four bytes of data into both <code>x</code> and <code>y</code>, while leaving the other parts of those structures unmodified (though it might in some cases be faster to only write the first seven bytes of the structure).  The C Standard, however, would have no way of allowing the last 25 bytes of each record from being written differently without allowing implementations to behave in completely arbitrary fashion.</p>

CC BY-SA 4.0
2023-09-07T20:03:05.547Z
2023-09-08T19:55:08.390Z
2023-09-08T19:55:08.390Z
supercat
CC BY-SA 4.0
2023-09-08T19:53:44.447Z
I see a definition for `test1` and for `test2`, but no definition for `test`, which is called by `test2`.  Am I missing something?
Brian
CC BY-SA 4.0
2023-09-08T19:59:20.897Z
@Brian: The `test2()` function was supposed to be calling `test1()`.  Code has been corrected.  The split between `test1()` and `test2()` was intended to make the point that a compiler processing code `test1()` may have no way of knowing what if anything downstream code would be doing with `x` and `y`, and the purpose of using `fwrite` was that it should be capable of writing without weird side effects any bit pattern that might be stored at the source address, and a compiler would have no way of knowing whether anything in the universe would care about the contents of any particular byte.
supercat
<p>It's not strictly <em>necessary</em> for uninitialised memory access to be undefined behaviour in the &quot;<a href="http://www.catb.org/jargon/html/N/nasal-demons.html" rel="noreferrer">nasal demons</a>&quot; sense. A language which allows raw memory access could still choose to specify some behaviour for otherwise-valid (i.e. properly aligned, no <a href="https://en.wikipedia.org/wiki/Page_fault" rel="noreferrer">page faults</a>, etc.) reads from memory ─ for example, it could result in an unspecified value for just that expression, without rendering the whole program as UB. Indeed, machine code itself is such a language.</p>
<p>The reason why C makes it UB is more to do with C's design philosophy than necessity. C is designed on the basis that the programmer (1) knows what they are doing, and (2) always wants the compiled output to be as efficient as possible.</p>
<p>So the language design logic is something like this: reading from uninitialised memory is so rarely useful that the language need not allow the programmer to do it. Then by (1) it's assumed that the programmer will take care that their code will never do it. And therefore, even if it looks to the compiler like a program <em>could</em> do that, the compiler is permitted to assume that it doesn't, which is good for (2) because the extra assumption might permit better object code to be generated.</p>
<hr />
<p>Now, we all know that this basically amounts to the unrealistic assumption that the programmer knows the C language spec inside-out and never makes mistakes; and real programs have had critical bugs (including serious security vulnerabilities) due to code written under the misapprehension that it's OK to read uninitialised memory if you don't use the value for anything. So to a large extent this kind of thing is a design flaw in the C programming language; and most modern languages are designed to be <a href="https://en.wikipedia.org/wiki/Memory_safety" rel="noreferrer">memory-safe</a> on the basis that real programmers aren't perfect and do make mistakes.</p>
<p>The counterpoint is that languages designed like this are needed for the same reason professional chefs need very sharp knives ─ they allow experts to do things that aren't possible with safer tools. The fact that the sharpest tools aren't suitable for all users or all uses doesn't necessarily mean that those tools are badly designed. I respect this argument, but it's ahistorical; C was designed to be a general-purpose language for widespread use.</p>
<p>In fact there are <a href="http://port70.net/%7Ensz/c/c99/n1256.html#J.2" rel="noreferrer">lots of things in the C standard that are or have been UB</a> which don't need to be. One of the most egregious examples is</p>
<blockquote>
<p>An unmatched <code>'</code> or <code>&quot;</code> character is encountered on a logical source line during tokenization</p>
</blockquote>
<p>If anyone knows of a sane reason that an unclosed string or character literal should be UB rather than a syntax error, I would love to hear it.</p>
<hr />
<p>As a side-note, since the question is about reading uninitialised raw memory, it's worth adding that a raw <em>write</em> to arbitrary memory really does have to be UB (if the language allows it at all); the memory written to might be the call stack, the program counter, or the executable code itself, so such a program really could have arbitrary behaviour (depending on the machine running the compiled code).</p>

CC BY-SA 4.0
2023-09-06T19:48:52.453Z
2024-05-07T12:04:04.000Z
2024-05-07T12:04:04.000Z
kaya3
CC BY-SA 4.0
2023-09-06T21:26:10.883Z
"Reading from uninitialised memory would be senseless" -- not true in all cases.  Consider a program declaring two unsigned variables `value` (uninitialized) and `mask` (initialized to 0).  Afterwards various conditional code might set both (or might not).  Finally, the values are used as `value & mask`.  While technically this is undefined behavior (`value` might be uninitialized) no reasonable compiler will misbehave here.  The only way to avoid the undefined behavior is to add a (wasteful) initialization of `value` (to what, it does not matter), or otherwise make the code less efficient.
Chris Dodd
CC BY-SA 4.0
2023-09-06T22:17:15.980Z
@ChrisDodd Yes, that's true. There's also the (probably more common) case of reading memory to a local variable, checking a condition for if that memory is initialised, and then using the local variable if the condition is true. I'll edit to clarify that.
kaya3
CC BY-SA 4.0
2023-09-07T00:07:14.917Z
The only "uninitialized memory" that exists in C is local (auto storage class) variables without initialization.  Even malloc returns initialized memory (albeit with unspecified value).
Chris Dodd
CC BY-SA 4.0
2023-09-07T09:19:24.670Z
"If anyone knows of a sane reason that an unclosed string or character literal should be UB rather than a syntax error, I would love to hear it." It allows more variety in parsing strategy, and allows for a theoretical `#pragma multilinestring`.
wizzwizz4
CC BY-SA 4.0
2023-09-07T13:42:18.043Z
"C is designed on the basis that the programmer [...] (2) always wants the compiled output to be as efficient as possible. So the language design logic is something like this: reading from uninitialised memory is so rarely useful that the language need not allow the programmer to do it." -- but is this true? Is this an original design philosophy of C, or is it and the related aggressive optimizations based on this something that only came later?
ilkkachu
CC BY-SA 4.0
2023-09-07T13:43:26.100Z
Because I seem to recall those optimization at least getting more common and more aggressive within the last, say, 10-15 years, and that really doesn't fit with C being called a "high-level assembler" or a language close to the hardware.
ilkkachu
CC BY-SA 4.0
2023-09-07T17:15:28.270Z
@ilkkachu I don't know if (2) was part of the design philosophy from the start ─ but updates to the language over the years also count as language design. There's a 2012 interview with Brian Kernighan [here](https://www.informit.com/articles/article.aspx?p=1960359) where he talks about C setting the standard for efficiency and being close to the hardware, which doesn't necessarily prove that it was a design goal but it does show how one of the creators of the language thinks about it.
kaya3
CC BY-SA 4.0
2023-09-07T17:25:38.010Z
@kaya3, in a way, yes. But if the part about that being undefined was there originally, then more recent developments can't well be the reason for that. There must have been some other reason originally.
ilkkachu
CC BY-SA 4.0
2023-09-07T17:53:08.100Z
@ilkkachu I think there are plenty of design decisions in C which point to efficiency being a design goal, even if I can't find a reference which authoritatively confirms that. Most of the UB in C could have been defined, except for the fact that defining it one way or another would force some platforms/architectures to implement it in a less efficient way (e.g. mandating two's-complement overflow behaviour would require emulation on one's-complement processors; compilers can emit faster code if they're permitted to assume the programmer has ensured that overflow won't happen).
kaya3
CC BY-SA 4.0
2023-09-07T20:17:50.283Z
@kaya3: I think the published C99 Rationale document makes things pretty clear.  Classifying an action as UB waived as a Quality of Implementation issue the question of whether and when implementations should document behavioral specifications or guarantees (e.g. that a certain computation will have no side effects beyond yielding a possibly-meaningless result) in certain situations.  People who are informed of this but continue to claim it was intended to invite gratuitously nonsensical behavior should be recognized as liars.
supercat
CC BY-SA 4.0
2023-09-07T20:19:45.427Z
@supercat Sorry, who claims that UB was intended to "invite gratuitously nonsensical behaviour"?
kaya3
CC BY-SA 4.0
2023-09-07T20:48:56.113Z
@kaya3: People trying to justify how clang and gcc process certain constructs.  If a compiler recognizes that integer overflow will occur in a function unless argument `x` is less than 32769, and consequently decides to bypass a guard clause `if (x < 32770)`, such action would only be justifiable if all possible behaviors would be equally acceptable, making the program nonsensical, if integer overflow occurs.
supercat
CC BY-SA 4.0
2023-09-07T21:15:38.617Z
@ilkkachu: C was designed to minimize the amount of compiler complexity required to produce *reasonably efficient* machine code.  In 1974, the notion that anyone would care about how efficiently a C program could be processed by a compiler that required more memory to run *than existed in all computers put together* would have been viewed as completely fanciful, and completely irrelevant to the goals of the language.
supercat
CC BY-SA 4.0
2023-09-07T21:20:17.700Z
@ilkkachu: Besides, I question the notion that most programmers would prefer a compiler that generates the most efficient program that could be produced from a strictly conforming source text, to one which supports useful features and guarantee beyond those mandated by the Standard, and would allow programs that exploit those features and guarantees to be even more efficient than the most efficient possible Strictly Conforming programs.
supercat
CC BY-SA 4.0
2023-09-07T22:15:49.433Z
@kaya3: With regard to unmatched quotes, because the Standard allows implementations to specify that they will behave usefully in cases where the Standard waives jurisdiction, a decision to waive jurisdiction in some corner case should not be interpreted as needing a reason beyond a belief that documenting the behavior would require more effort than it would be worth.  If one implementation would interpret a newline within a string literal as though preceded by a close quote, another would treat a newline as part of the literal, and others might behave in various other ways, trying to...
supercat
CC BY-SA 4.0
2023-09-07T22:16:11.773Z
...specify any behavior upon which programmers could rely would be difficult.
supercat
CC BY-SA 4.0
2023-09-08T14:43:56.510Z
Off-topic: what knife-equivalent tool is safer than a very sharp knife? (An unsharp knife is not a knife but a stick.)
Pablo H
CC BY-SA 4.0
2023-09-08T14:53:03.610Z
@PabloH: A knife with a very firm handle is safer than a knife with a wobbly handle that hopefully lets the blade follow the grain contours of the material being cut.  The latter may allow some materials to be cut more quickly and with less effort than a knife with a firm handle, but should be recognized as being suitable for a more limited range of tasks, as as being dangerous for other tasks.
supercat
CC BY-SA 4.0
2023-09-09T03:15:11.750Z
@supercat You've plastered the entire page with complaints about gcc and clang making decisions no one wants. I don't think anyone is buying your argument. Why don't you actually prove that, write your ideal compiler and see if it gets widespread adoption?
Passer By
CC BY-SA 4.0
2023-09-10T23:56:08.683Z
@PasserBy: You asked who would claim UB was intended to justify graduitously nonsensical behavior.  I answered.  I think I'm the only person responding to the original question who recognizes the possibility of *some* useful optimizations which would be inconsistent with uninitialized objects simply holding indeterminate bit patterns, but useful optimizations would be predicated upon the notion that certain corner cases should have no side effects beyond the fact that some of the values values they compute woul likely be meaningless, and would be undermined by processing code nonsensically.
supercat
<p>I am not sure if this was in the C standardisation committee's mind when making this decision, but it is worth noting that some computers may perform arbitrary actions when memory is read.  A memory read is generally translated by the processor into a signal on a bus that is connected to that processor, and devices connected to the bus may behave however they wish in conjunction with that.  Thus reading from an arbitrary address may have arbitrary effects.</p>
<p>One real-world example is the Apple II computer, which had various addresses that changed system behaviour when read from, which were typically called &quot;soft switches&quot;.  Reading from addresses in the range 0xC010-0xC017 will cause various sections of memory to be paged in and out (potentially including the memory containing the code being executed).  Reading from addresses between 0xC018-0xC01F will change display modes, etc.</p>
<p>A more modern example might be memory-mapped registers in various microcontrollers, for example in AVR microcontrollers such as the <a href="https://ww1.microchip.com/downloads/aemDocuments/documents/MCU08/ProductDocuments/DataSheets/ATtiny202-204-402-404-406-DataSheet-DS40002318A.pdf" rel="noreferrer">ATtiny402</a> there are a number of addresses that correspond to the low byte of a 16-bit register.  When these are read, a different memory address's value (the <code>TEMP</code> register) changes to the current value of the high byte of the same register, thus allowing two different 8-bit read operations to reliably access a 16-bit value that may change asynchronously.</p>
<p>I'm not sure what the best way the C specification could have used to specify that this kind of behaviour could happen, but it is important to note that memory reads are not necessarily entirely without side effects.</p>

CC BY-SA 4.0
2023-09-06T22:02:16.883Z
2023-09-06T22:02:16.883Z
occipita
CC BY-SA 4.0
2023-09-06T22:06:08.723Z
This is a good point, but it applies to reading from arbitrary memory addresses ─ as opposed to known addresses of memory which isn't initialised *yet*, like uninitialised local variables. Presumably a local variable would not be stored in one of these special addresses which has side-effects when read.
kaya3
CC BY-SA 4.0
2023-09-06T22:10:09.740Z
True.  It's been a while since I read the C standard (and there have been a number of updates to it since then), but IIRC it does not distinguish between these categories, This may be an intentional simplification in order to keep the standard manageable.
occipita
CC BY-SA 4.0
2023-09-06T22:13:58.533Z
The undefined behavior only applies when reading "an object of automatic storage duration that could have been declared with the register storage class (never had its address taken), and that object is uninitialized" (C11 spec 6.3.2.1.2) -- a local var -- so would not apply to global things like memory-mapped I/O (which would necessarily be implementation-dependent)
Chris Dodd
CC BY-SA 4.0
2023-09-06T23:00:22.613Z
Just as a comment on this, the C committee (and, indeed, K&R themselves) did think of memory-mapped I/O. That is what "volatile" is for.
Pseudonym
CC BY-SA 4.0
2023-09-07T21:22:09.023Z
A more interesting address on the Apple II would be 0xC0EF.  Reading that address within about a second of a disk access via controller in slot 6 will likely overwrite a track of data on the last accessed drive.
supercat
<blockquote>
<p>For what purpose would a language designer declare any uninitialized memory access undefined behavior (program behavior is meaningless) as opposed to merely unspecified (the value contained could be one of any number of possible values unpredictably)</p>
</blockquote>
<p>A big question is how you obtain a pointer to the uninitialized memory.</p>
<p>The simplest case is an uninitialized stack variable. Modern systems allocate stack page-by-page on demand. Every system I know does so on any access, but it is conceivable that some system could do it only on first write. That could even be useful to catch some out-of-bounds read errors, and throw a segmentation fault.</p>
<p>The way C standard is written, it avoids over-defining behaviors when there is no useful purpose for them. It's hardly more useful to read an unspecified value than to potentially crash, thus it is not defined.</p>
<p>Note that this does not limit implementations: any implementation can define that access to uninitialized stack variable successfully reads unspecified value. If the implementation does stack scrubbing, it could even specify that uninitialized stack variables read out as zeros. Sadly, many implementations do not have good documentation on such behaviors.</p>

CC BY-SA 4.0
2023-09-07T10:28:18.787Z
2023-09-07T10:28:18.787Z
jpa
CC BY-SA 4.0
2023-09-07T20:20:54.100Z
//The way C standard is written, it avoids over-defining behaviors when there is no useful purpose for them. // It goes further than that, and goes out of its way to ensure that if a useful optimizing transform might yield program behavior that's in any way observably different from the original, at least one action in that execution will be classified as UB, even if all individual actions within the program would otherwise have fully defined behavior.
supercat
<p>On top of what everyone else has said, UB is also often used to signal the fact that a program could mean different things at different optimisation levels.</p>
<p>Consider this example:</p>
<pre class="lang-c prettyprint-override"><code>int
foo(int x)
{
    int y;
    if (x &gt; 42) {
        y = 5;
    }
    return y;
}
</code></pre>
<p>If you call <code>foo(3)</code>, it <em>might</em> return whatever value was already in the location (whether register or stack) where <code>y</code> happens to be assigned. The returned value could be different on different calls. This function, even though it doesn't mutate or read &quot;global state&quot;, is not referentially transparent.</p>
<p>But an optimising compiler might optimise it to this:</p>
<pre class="lang-c prettyprint-override"><code>int
foo(int x)
{
    return 5;
}
</code></pre>
<p>This is a completely valid transformation. By using an uninitialised variable, you, the programmer, effectively told the compiler that you <em>did not care</em> what value was returned if <code>x &lt;= 42</code>. If you don't care, then the compiler gets to pick any value it wants.</p>
<p>By the way, if you're curious how to implement this in a compiler, it's quite straightforward if you already know about abstract interpretation. Typically, we use the <em>bottom</em> element <span class="math-container">$\bot$</span> of the domain lattice as &quot;don't know&quot;, but what does the <em>top</em> element <span class="math-container">$\top$</span> mean?</p>
<p>It turns out it means, more or less, &quot;don't care&quot;, and this is exactly the situation where that is useful. After the if-then join point, you take the greatest lower bound of <span class="math-container">$\top$</span> and <span class="math-container">$\{ 5\}$</span>, and get <span class="math-container">$\{5\}$</span>. The compiler can then conclude that assuming that <code>y</code> always equals <span class="math-container">$5$</span> is safe.</p>
<p>It's also legal for a compiler to assign a &quot;Heisenberg&quot; value to an uninitialised variable: it doesn't have a fixed value until you look. Such &quot;values&quot; don't behave like a variable which was initialised with one (possibly random) value when it was created.</p>
<p>Here's a highly artificial example:</p>
<pre class="lang-c prettyprint-override"><code>int
foo(int x)
{
    int y;
    if (x &lt; 100) {
        if (x &lt; 50) {
            y = 5;
        }
    }
    else {
        if (x &lt; 150) {
            y = 10;
        }
    }
    return y;
}
</code></pre>
<p>This is a legal optimisation:</p>
<pre class="lang-c prettyprint-override"><code>int
foo(int x)
{
    int y;
    if (x &lt; 100) {
        y = 5;
    }
    else {
        y = 10;
    }
    return y;
}
</code></pre>
<p>But there is no value that you could &quot;initially&quot; assign to <code>y</code> that would result in this optimised code.</p>

CC BY-SA 4.0
2023-09-06T22:51:23.697Z
2023-09-06T22:58:09.550Z
2023-09-06T22:58:09.550Z
Pseudonym
CC BY-SA 4.0
2023-09-07T18:11:23.220Z
Even worse, an uninitialised variable can appear to take on a different value every time you look at it, so that you can get output from: `int a; if (a < 0 && a>0) puts("a is uninitialised");` (In practice this is unlikely, as optimizers generally use inferences from previous checked conditions, but they're not obligated to.)
Martin Kealey
CC BY-SA 4.0
2023-09-07T20:06:26.517Z
@MartinKealey: As described in my answer, there are situations where allowing values to behave as a non-deterministic union of possible states may facilitate useful optimizations that may be consistent with application requirements even if they're not consistent with deterministic sequential processing, but clang and gcc interpret UB as an invitation to behave in gratuitously nonsensical behavior which throws all laws of causality out the window.
supercat
CC BY-SA 4.0
2023-09-08T18:10:02.533Z
A C compiler is allowed to assume that undefined behaviour never happens. If follows that x>42 and y=5. It cannot be  anything else without undefined behaviour.
gnasher729
CC BY-SA 4.0
2023-09-08T19:50:10.550Z
@gnasher729: A C compiler which can correctly process a contrived and useless program that nominally exercises the translation limits in N1570 5.2.4.1 is allowed to behave in completely arbitrary fashion when given almost any other program.  The authors of the Standard have stated in the Rationale that they expected quality implementations to behave usefully in more cases than mandated by the Standard, though they waived jurisdiction over what cases those would be.
supercat
CC BY-SA 4.0
2023-09-09T06:06:17.210Z
I'm pretty sure the lattice you are describing is upside down from the usual way it's presented (doesn't really matter, but it is interesting)
Oscar Smith
<p>Many instances of undefined behaviour in the C specification represent accommodations for existing implementations where the &quot;obvious&quot; treatment was not possible, including detecting and diagnosing it. For example, reading a pointer to uninitialised memory may trigger memory-mapped effects on some architectures, beyond merely producing an unspecified result, or local variables may run into hardware guards on register access. These are exactly the &quot;nasal demons&quot; scenarios where <strong>effects outside the language can occur</strong>. These platforms are less common today, though many embedded architectures do have unusual behaviours to someone accustomed to x86 or ARM.</p>
<p>A language that wishes to compile directly to these platforms, or a specification for a language with existing implementations on them, will need to address these situations. The C specification is in this category, and encoded the commonality of what existed, not a from-scratch definition of what ought to be. It gives some minimum portable guarantees, but also accommodated the (possibly bad) choices made already.</p>
<p>A <em>new</em> language probably doesn't need to have any of this. These sorts of systems may be irrelevant enough to rule out, or it may be able to define its memory model sufficiently to avoid this coming up, requiring implementations for those targets to do extra work to accommodate it. It may just make uninitialised access inexpressible at the language level. Given the much-reduced compatibility costs, it would probably be the right choice.</p>

CC BY-SA 4.0
2023-09-06T21:52:58.003Z
2023-09-06T21:52:58.003Z
Michael Homer
CC BY-SA 4.0
2023-09-06T22:04:08.420Z
Huh.  Guess we both had the same thought at the same time... :)
occipita
CC BY-SA 4.0
2023-09-07T22:43:10.970Z
I don't think there was ever really any doubt as to how an implementation for the Apple II should be expected to behave on a typical machine if code attempts to read-dereference a character pointer whose bit representation matches integer value 0xC0E9.  The compiler may have no idea what that would do (beyond issuing a read of address $C0E9), but someone adequately familiar with the hardware should recognize that such an action would turn on the drive-motor control for a floppy controller in slot 6, but "modern" UB has ceased to be a hardware issue.
supercat
<p>If nothing else, it simplifies the language specification, and as a consequence makes it easier for programmers to understand the language, and also makes things easier for implementors.</p>
<p>As you say, some types may have no trap representations, but others might. But distinguishing access to uninitialized variables based on the type adds complexity to the specification. Programmers will have to take note of the types of their variables when taking advantage of being allowed to access them. Compiler writers will need different optimization code depending on the variable types.</p>
<p>And what do we gain from this added complexity? Not much. There's rarely an important reason to access uninitialized memory. An exception might be something like assigning a struct that has some uninitialized members, or <code>memcpy()</code> a buffer that may only have a portion filled in (although the application should usually know where the initialized portion ends and can use that as the copy size).</p>
<p>In practice, nothing bad is actually likely to happen from these accesses. While it may be UB to use <code>memcpy()</code> when some portion of the copied memory is uninitialized, it's not UB to use <code>realloc()</code> on a pointer to uninitialized data. Yet <code>realloc()</code> effectively has to do the same <code>memcpy()</code> when it needs to relocate the allocation -- there are few architectures where <code>realloc()</code> can tell which portions are allocated.</p>

CC BY-SA 4.0
2023-09-07T15:30:07.740Z
2023-09-07T15:30:07.740Z
Barmar
CC BY-SA 4.0
2023-09-07T20:26:01.177Z
Most of the useful optimizing transforms that would be facilitated by treating uninitialized storage with semantics looser than "Unspecified Value" could only be performed in programs that would sometimes read uninitialized storage in cases where the read value wouldn't matter.  If programmers have to ensure that all storage is written in able to be able to perform any meaningful tasks, nothing will be gained by facilitating such optimizations, meaning that the rules allowing them would have only negative effects.
supercat
CC BY-SA 4.0
2023-09-07T20:31:01.300Z
I'm not sure if you're supporting or contradicting me. :)
Barmar
CC BY-SA 4.0
2023-09-07T20:37:53.153Z
The benefits from allowing programmers to access uninitialized storage in situations where all bit patterns would equally satisfy application requirements may not be particularly huge, but they're greater than the performance benefits from allowing compilers to behave in completely arbitrary fashion if such accesses occur.
supercat
<p>Jens Gustedt, who is involved in the C standardization, has addressed this <a href="https://stackoverflow.com/a/11965368/3150802">in an answer on SO</a>.</p>
<p>The executive summary:</p>
<p><strong>Reading uninitialized memory is not <em>necessarily</em> undefined behavior in C.</strong></p>
<p>There are exactly two things that can go wrong.</p>
<ol>
<li>The type has trap representations, and that happens to be the representation in that memory location;</li>
<li>An object with automatic storage duration (i.e., a local variable or a parameter) may be held in a register. The reason this is UB is that there are platforms which have special provisions to prevent read before write from registers, as an error detection.</li>
</ol>
<p>UB is restricted to these two possibilities. We know that there are no trap representations. If your variable does not have automatic storage duration, or if you take its address — which prevents it from being in a register — you are safe.</p>

CC BY-SA 4.0
2023-09-08T21:25:39.297Z
2023-09-09T08:45:40.840Z
2023-09-09T08:45:40.840Z
Peter - Reinstate Monica
CC BY-SA 4.0
2023-09-11T00:10:59.347Z
The notion that things depend on whether things "can" be kept on registers fails Interestingly; gcc-ARM used to process uninitialized automatic-duration objects of type `uint16_t` in a manner inconsistent with any bit pattern such objects could hold, even in cases where their addresses were taken, and has fixed that behavior by unconditionally initializing the associated registers to zero even in cases where their addresses aren't taken.
supercat
<p>The &quot;as if&quot; rule means that a variable need not have a fixed address in memory unless you take its address (and keep that in a pointer variable somewhere else).</p>
<p>This can be used by optimizers to store the &quot;current&quot; value of a single variable in a succession of different locations, including multiple registers, and maybe even multiple memory addresses (*1).</p>
<p>So when some branch of the code causes the compiler to ask its register/variable tracker &quot;where is the value of this variable&quot;, and the data flow analysis proves that it's uninitialised, the tracker isn't allowed to say &quot;dunno&quot; (because that would abort the compilation), so it may simply choose a random register and say &quot;in here&quot;.</p>
<p>Even if it always picks the same dummy register, it might also get used for something else, so the apparent value of the variable isn't just &quot;unspecified&quot;, it's random <em>every time</em> it's read.</p>
<p>For example, given this:</p>
<pre><code>extern int foo();
void func() {
  int a;
  if (a &amp;&amp; foo() &amp;&amp; !a)
    puts(&quot;a is uninitialised&quot;);
}
</code></pre>
<p>clearly <code>foo</code> can't see <code>a</code>, nor intentionally alter it, and yet the apparent value of <code>a</code> could change because <code>foo</code> is compiled to something that uses the register that also gets used when <code>a</code> is read. Since <code>a</code> doesn't &quot;own&quot; the register, it needn't be saved while <code>foo</code> is called.</p>
<p>At the same time, the compiler could look at <code>a &amp;&amp; ... !a</code> and decide that that can &quot;never happen&quot;, and remove the code for the <code>puts</code> statement entirely. So when <code>foo</code> is called and comes back with <code>true</code>, there are random bytes where the program code should be. Then we get to watch the parade of nasal daemons.</p>
<p>(*1: Some CPU's can do <code>PUSH</code> and <code>POP</code> faster than general memory writing &amp; reading, and the compiler could take advantage of that when it needs to spill registers, rather than always writing to locations based on which variable the register currently holds.)</p>

CC BY-SA 4.0
2023-09-07T18:36:25.603Z
2023-09-07T18:36:25.603Z
Martin Kealey
CC BY-SA 4.0
2023-09-07T20:35:29.183Z
An abstraction model that recognized the possibility of indeterminate or even non-deterministic values, but limited the range of possible actions to those that were within the set of actual possibilities (so that evaluating e.g. `(x & 3)+(x & 9)` when `x` is uninitialized might yield 0, 1, 2, 3, 4, 8, 9, 10, 11, or 12, but those would be the only possible behaviors) would allow more useful optimizations than one that requires that programmers add otherwise-unnecessary code that would make such optimizations irrelevant.
supercat
CC BY-SA 4.0
2023-09-14T11:41:17.483Z
@supercat Interesting that odd numbers are among the suggested results, indicating that `x` might not give the same value twice. Another real possibility is that an uninitialised variable could result in a trap when read. This really did happen on the Intel IA64, where registers had an extra `NAT` _Not A Thing_ bit.
Martin Kealey
CC BY-SA 4.0
2023-09-14T15:06:45.417Z
If a value is read multiple times, the reads might yield different values.  Another possible source of weirdness I meant to mention in my answer (tell me if you think it could fit in my answer, or might be better as another answer) is that if a value smaller than a register is optimized into a register, reading it might yield the contents of that register whether or not it's within range of the value.  IMHO, a good abstraction model for general-purpose implementations should allow for this possibility, but *only* if a compiler refrains from making its own downstream assumptions...
supercat
CC BY-SA 4.0
2023-09-14T15:10:58.673Z
...about the range of possible values after the value is copied to some other object.  Given e.g. `unsigned char x; int y; ... y=x; if (y < 256) do_something(y);` a compiler should have the choice of either processing `y=x;` in a way that would set `y` to a value 0..255 even if `x` was unintialized and then omit the `if` check, or processing it in a way that might set `y` to a value over 255 but then skip the `do_something()` call, but a general-purpose compiler should only be allowed to assume `y` will be less than 256 if its generated code would make `y` less than 256.
supercat
<p><strong>TL;DR: It's golden for Static Analysis.</strong></p>
<h3>A dreadful example</h3>
<p>One of the issues with defined behavior, is that readers can no longer distinguish between accidents and intent.</p>
<p>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 &quot;temporarily&quot; overflowing (a - b + c = a + c - b). But there's also plenty of cases where overflows are just <em>errors</em>, some of them leading to catastrophic failures (<code>malloc(n_items * sizeof *items)</code> being particular bad =&gt; use <code>calloc</code> instead).</p>
<p>The problem, though, is that because it has been <em>defined</em>, 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.</p>
<h3>Reading from uninitialized memory</h3>
<p>In <em>general</em>, 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.</p>
<p>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.</p>
<h3>Alternative: Debug vs Release</h3>
<p>The Rust language offers a more interesting alternative.</p>
<p>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 :(</p>
<p>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:</p>
<ul>
<li>Wrap on overflow, in Release, by default.</li>
<li>Panic on overflow, in Debug, by default.</li>
</ul>
<p>(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)</p>
<p>The exact options selected matter less, for the purpose of generalizing the rule, than the <em>divergence</em> between Debug and Release behavior. This divergence of behavior essentially codifies that despite being specified, overflow is necessarily an accident.</p>
<p>How to apply this divergent behavior to reads from uninitialized memory is left as an exercise to the reader.</p>

CC BY-SA 4.0
2023-09-07T17:22:45.020Z
2023-09-07T17:22:45.020Z
Matthieu M.
CC BY-SA 4.0
2023-09-07T20:07:33.387Z
I find this argument unconvincing. Making it a compiler error or runtime error would be the best way to ensure that nobody thinks it's intentional. If the goal is for bugs to be more visible, then it is perverse to give the compiler permission to transform a mistake into arbitrary behaviour and not notify the programmer that it has done so. That arbitrary behaviour might be only very subtly or very rarely different to what was actually intended, and then the bug goes unnoticed for a long time.
kaya3
CC BY-SA 4.0
2023-09-07T20:31:10.597Z
@kaya3: More generally, there are many situations where some tasks will be facilitated by an ability to do some things, but other tasks would never deliberately do such things and it would be useful to trap if a program attempts them.  What would be most helpful would be recognizing that such actions may be incompatible with some useful diagnostic tools, and questions about the relative usefulness of the actions versus the tools should be left up to programmers.  Any attempted "one-size-fits-all" compromise will end up doing a bad job of fitting conflicting needs.
supercat
CC BY-SA 4.0
2023-09-07T22:21:50.187Z
How does characterizing an action as resulting from a "non-portable or erroneous program construct", and waiving jurisdiction over it, imply any judgment that the behavior could not have been intended?
supercat
CC BY-SA 4.0
2023-09-08T06:54:30.203Z
@kaya3: It's not about perversion, it's about humility and pragmatism. Not all kinds of bugs are easily detected at compile-time, or even at run-time. Not all kinds of bugs have easy remediation. Use of uninitialized memory, like use-after-free, belongs to the most difficult to diagnose bugs. ASan (Address Sanitizer) which detects some of those has non-trivial run-time overhead. If you can't efficiently catch all occurrences of such an error, then you have a hard question in front of you.
Matthieu M.
CC BY-SA 4.0
2023-09-08T06:59:43.780Z
@supercat: It seems obvious to me. If neither the standard nor the compiler ("implementation") you use define the behavior, then what can you expect from it? Nothing and Everything. If the mere presence of the construct completely voids any purpose of your program, then it's either an error or art... and nobody cares about diagnosis for the latter.
Matthieu M.
CC BY-SA 4.0
2023-09-08T14:44:26.633Z
@MatthieuM.: Which is a simpler rule: reading an object whose storage holds a valid bit pattern always yields the value associated with that bit pattern, or [as above] only if other conditions apply?  If a program is designed in such a way that the former described action would satisfy application requirements for every possible bit pattern a region of storage could hold, the program may be "non-portable" to implementations where it would be possible for storage to hold an invalid bit pattern, but that would not imply that it wasn't 100% correct on platforms where all bit patterns were valid.
supercat
CC BY-SA 4.0
2023-09-08T14:51:11.323Z
@supercat: I don't care for simplicity as much as I care for correctness. I'd rather get a diagnosis -- even, begrudgingly, a run-time diagnosis when running under a validator -- than let garbage data flow freely into the computations. As Babbage said: Garbage In, Garbage Out.
Matthieu M.
CC BY-SA 4.0
2023-09-08T14:59:24.560Z
@MatthieuM.: If C included a construct that meant "treat some range of storage as having been written with whatever byte pattern it happens to contain", then it would be reasonable to deprecate code that relies upon such behavior without using such a construct.  Further, a good standard should make clear that "diagnostic" implementations may trap on *any* action a programmer knows code isn't doing deliberately, without regard for whether the behavior of the action would otherwise be defined.  As an analogy, a tool which on program exit reports any storage that has been malloc'ed but not...
supercat
CC BY-SA 4.0
2023-09-08T15:20:25.850Z
...released may be far more useful with a program that releases all storage before it exits than one which skips such actions, but that doesn't mean a program that exits without first taking the time to release memory should be viewed as "defective".  The fact that some constructs would undermine the usefulness of some diagnostic tools doesn't mean they should be universally forbidden, but merely viewed as something programmers should factor into their judgments about how to do things.
supercat
CC BY-SA 4.0
2024-05-08T19:48:05.457Z
"Making it a compiler error or runtime error" often requires runtime checks that are likely to have a prohibitive effect on performance (there are cases where the compiler can detect it statically, but this is usually impossible when pointers or conditionals are involved). When it can be detected statically, IDEs now will warn.
Barmar
# Why would accessing uninitialized memory necessarily be undefined behavior?
Posted by: username_0 on 2023-09-06
In C, accessing any indeterminate/uninitialized memory is undefined behavior, period. Even in the case that the type in question is guaranteed to have no trap representations, such as `unsigned char` or any of the optional fixed-width integer types.

On of the reasons I thought of as to why uninitialized memory access is undefined, is because there might be a trap representation. But if the type being used to access the memory has none, this is irrelevant.

For what purpose would a language designer declare any uninitialized memory access undefined behavior (program behavior is meaningless) as opposed to merely unspecified (the value contained could be one of any number of possible values unpredictably), especially with types that do not have trap representations?
**Tags:** c memory-safety undefined-behavior
> **Comments:**
> **username_1**: Why is the word "necessarily" in the title of this question? The nature of "undefined" means that's not _necessarily_ anything in particular.  
> **username_0**: @MartinKealey My question was more: why was it necessary for them to not define the behavior?  
> **username_1**: see answer below  
> **username_2**: No it isn't necessarily undefined behavior, memory allocated with `malloc` and `realloc` can be read from without being initialized without causing undefined behavior as long as you are using a type with no trap representations.  
---
## 10 answers
### Answer by username_5 on 2023-09-06. Score: 20 | Accepted answer
It's not strictly *necessary* for uninitialised memory access to be undefined behaviour in the "[nasal demons](http://www.catb.org/jargon/html/N/nasal-demons.html)" sense. A language which allows raw memory access could still choose to specify some behaviour for otherwise-valid (i.e. properly aligned, no [page faults](https://en.wikipedia.org/wiki/Page_fault), etc.) reads from memory ─ for example, it could result in an unspecified value for just that expression, without rendering the whole program as UB. Indeed, machine code itself is such a language.

The reason why C makes it UB is more to do with C's design philosophy than necessity. C is designed on the basis that the programmer (1) knows what they are doing, and (2) always wants the compiled output to be as efficient as possible.

So the language design logic is something like this: reading from uninitialised memory is so rarely useful that the language need not allow the programmer to do it. Then by (1) it's assumed that the programmer will take care that their code will never do it. And therefore, even if it looks to the compiler like a program *could* do that, the compiler is permitted to assume that it doesn't, which is good for (2) because the extra assumption might permit better object code to be generated.

---

Now, we all know that this basically amounts to the unrealistic assumption that the programmer knows the C language spec inside-out and never makes mistakes; and real programs have had critical bugs (including serious security vulnerabilities) due to code written under the misapprehension that it's OK to read uninitialised memory if you don't use the value for anything. So to a large extent this kind of thing is a design flaw in the C programming language; and most modern languages are designed to be [memory-safe](https://en.wikipedia.org/wiki/Memory_safety) on the basis that real programmers aren't perfect and do make mistakes.

The counterpoint is that languages designed like this are needed for the same reason professional chefs need very sharp knives ─ they allow experts to do things that aren't possible with safer tools. The fact that the sharpest tools aren't suitable for all users or all uses doesn't necessarily mean that those tools are badly designed. I respect this argument, but it's ahistorical; C was designed to be a general-purpose language for widespread use.

In fact there are [lots of things in the C standard that are or have been UB](http://port70.net/%7Ensz/c/c99/n1256.html#J.2) which don't need to be. One of the most egregious examples is

> An unmatched `'` or `"` character is encountered on a logical source line during tokenization

If anyone knows of a sane reason that an unclosed string or character literal should be UB rather than a syntax error, I would love to hear it.

---

As a side-note, since the question is about reading uninitialised raw memory, it's worth adding that a raw *write* to arbitrary memory really does have to be UB (if the language allows it at all); the memory written to might be the call stack, the program counter, or the executable code itself, so such a program really could have arbitrary behaviour (depending on the machine running the compiled code).
> **Comments:**
> **username_6** (2023-09-06): "Reading from uninitialised memory would be senseless" -- not true in all cases.  Consider a program declaring two unsigned variables `value` (uninitialized) and `mask` (initialized to 0).  Afterwards various conditional code might set both (or might not).  Finally, the values are used as `value & mask`.  While technically this is undefined behavior (`value` might be uninitialized) no reasonable compiler will misbehave here.  The only way to avoid the undefined behavior is to add a (wasteful) initialization of `value` (to what, it does not matter), or otherwise make the code less efficient.  
> **username_5** (2023-09-06): @ChrisDodd Yes, that's true. There's also the (probably more common) case of reading memory to a local variable, checking a condition for if that memory is initialised, and then using the local variable if the condition is true. I'll edit to clarify that.  
> **username_6** (2023-09-07): The only "uninitialized memory" that exists in C is local (auto storage class) variables without initialization.  Even malloc returns initialized memory (albeit with unspecified value).  
> **username_7** (2023-09-07): "If anyone knows of a sane reason that an unclosed string or character literal should be UB rather than a syntax error, I would love to hear it." It allows more variety in parsing strategy, and allows for a theoretical `#pragma multilinestring`.  
> **username_8** (2023-09-07): "C is designed on the basis that the programmer [...] (2) always wants the compiled output to be as efficient as possible. So the language design logic is something like this: reading from uninitialised memory is so rarely useful that the language need not allow the programmer to do it." -- but is this true? Is this an original design philosophy of C, or is it and the related aggressive optimizations based on this something that only came later?  
> **username_8** (2023-09-07): Because I seem to recall those optimization at least getting more common and more aggressive within the last, say, 10-15 years, and that really doesn't fit with C being called a "high-level assembler" or a language close to the hardware.  
> **username_5** (2023-09-07): @ilkkachu I don't know if (2) was part of the design philosophy from the start ─ but updates to the language over the years also count as language design. There's a 2012 interview with Brian Kernighan [here](https://www.informit.com/articles/article.aspx?p=1960359) where he talks about C setting the standard for efficiency and being close to the hardware, which doesn't necessarily prove that it was a design goal but it does show how one of the creators of the language thinks about it.  
> **username_8** (2023-09-07): @kaya3, in a way, yes. But if the part about that being undefined was there originally, then more recent developments can't well be the reason for that. There must have been some other reason originally.  
> **username_5** (2023-09-07): @ilkkachu I think there are plenty of design decisions in C which point to efficiency being a design goal, even if I can't find a reference which authoritatively confirms that. Most of the UB in C could have been defined, except for the fact that defining it one way or another would force some platforms/architectures to implement it in a less efficient way (e.g. mandating two's-complement overflow behaviour would require emulation on one's-complement processors; compilers can emit faster code if they're permitted to assume the programmer has ensured that overflow won't happen).  
> **username_3** (2023-09-07): @kaya3: I think the published C99 Rationale document makes things pretty clear.  Classifying an action as UB waived as a Quality of Implementation issue the question of whether and when implementations should document behavioral specifications or guarantees (e.g. that a certain computation will have no side effects beyond yielding a possibly-meaningless result) in certain situations.  People who are informed of this but continue to claim it was intended to invite gratuitously nonsensical behavior should be recognized as liars.  
> **username_5** (2023-09-07): @supercat Sorry, who claims that UB was intended to "invite gratuitously nonsensical behaviour"?  
> **username_3** (2023-09-07): @kaya3: People trying to justify how clang and gcc process certain constructs.  If a compiler recognizes that integer overflow will occur in a function unless argument `x` is less than 32769, and consequently decides to bypass a guard clause `if (x < 32770)`, such action would only be justifiable if all possible behaviors would be equally acceptable, making the program nonsensical, if integer overflow occurs.  
> **username_3** (2023-09-07): @ilkkachu: C was designed to minimize the amount of compiler complexity required to produce *reasonably efficient* machine code.  In 1974, the notion that anyone would care about how efficiently a C program could be processed by a compiler that required more memory to run *than existed in all computers put together* would have been viewed as completely fanciful, and completely irrelevant to the goals of the language.  
> **username_3** (2023-09-07): @ilkkachu: Besides, I question the notion that most programmers would prefer a compiler that generates the most efficient program that could be produced from a strictly conforming source text, to one which supports useful features and guarantee beyond those mandated by the Standard, and would allow programs that exploit those features and guarantees to be even more efficient than the most efficient possible Strictly Conforming programs.  
> **username_3** (2023-09-07): @kaya3: With regard to unmatched quotes, because the Standard allows implementations to specify that they will behave usefully in cases where the Standard waives jurisdiction, a decision to waive jurisdiction in some corner case should not be interpreted as needing a reason beyond a belief that documenting the behavior would require more effort than it would be worth.  If one implementation would interpret a newline within a string literal as though preceded by a close quote, another would treat a newline as part of the literal, and others might behave in various other ways, trying to...  
> **username_3** (2023-09-07): ...specify any behavior upon which programmers could rely would be difficult.  
> **username_9** (2023-09-08): Off-topic: what knife-equivalent tool is safer than a very sharp knife? (An unsharp knife is not a knife but a stick.)  
> **username_3** (2023-09-08): @PabloH: A knife with a very firm handle is safer than a knife with a wobbly handle that hopefully lets the blade follow the grain contours of the material being cut.  The latter may allow some materials to be cut more quickly and with less effort than a knife with a firm handle, but should be recognized as being suitable for a more limited range of tasks, as as being dangerous for other tasks.  
> **username_10** (2023-09-09): @supercat You've plastered the entire page with complaints about gcc and clang making decisions no one wants. I don't think anyone is buying your argument. Why don't you actually prove that, write your ideal compiler and see if it gets widespread adoption?  
> **username_3** (2023-09-10): @PasserBy: You asked who would claim UB was intended to justify graduitously nonsensical behavior.  I answered.  I think I'm the only person responding to the original question who recognizes the possibility of *some* useful optimizations which would be inconsistent with uninitialized objects simply holding indeterminate bit patterns, but useful optimizations would be predicated upon the notion that certain corner cases should have no side effects beyond the fact that some of the values values they compute woul likely be meaningless, and would be undermined by processing code nonsensically.  
---
### Answer by username_12 on 2023-09-06. Score: 29
On top of what everyone else has said, UB is also often used to signal the fact that a program could mean different things at different optimisation levels.
Consider this example:
int
foo(int x)
{
    int y;
    if (x > 42) {
        y = 5;
    }
    return y;
}

If you call foo(3), it might return whatever value was already in the location (whether register or stack) where y happens to be assigned. The returned value could be different on different calls. This function, even though it doesn't mutate or read "global state", is not referentially transparent.
But an optimising compiler might optimise it to this:
int
foo(int x)
{
    return 5;
}

This is a completely valid transformation. By using an uninitialised variable, you, the programmer, effectively told the compiler that you did not care what value was returned if x <= 42. If you don't care, then the compiler gets to pick any value it wants.
By the way, if you're curious how to implement this in a compiler, it's quite straightforward if you already know about abstract interpretation. Typically, we use the bottom element $\bot$ of the domain lattice as "don't know", but what does the top element $\top$ mean?
It turns out it means, more or less, "don't care", and this is exactly the situation where that is useful. After the if-then join point, you take the greatest lower bound of $\top$ and $\{ 5\}$, and get $\{5\}$. The compiler can then conclude that assuming that y always equals $5$ is safe.
It's also legal for a compiler to assign a "Heisenberg" value to an uninitialised variable: it doesn't have a fixed value until you look. Such "values" don't behave like a variable which was initialised with one (possibly random) value when it was created.
Here's a highly artificial example:
int
foo(int x)
{
    int y;
    if (x < 100) {
        if (x < 50) {
            y = 5;
        }
    }
    else {
        if (x < 150) {
            y = 10;
        }
    }
    return y;
}

This is a legal optimisation:
int
foo(int x)
{
    int y;
    if (x < 100) {
        y = 5;
    }
    else {
        y = 10;
    }
    return y;
}

But there is no value that you could "initially" assign to y that would result in this optimised code.
> **Comments:**
> **username_1** (2023-09-07): Even worse, an uninitialised variable can appear to take on a different value every time you look at it, so that you can get output from: `int a; if (a < 0 && a>0) puts("a is uninitialised");` (In practice this is unlikely, as optimizers generally use inferences from previous checked conditions, but they're not obligated to.)  
> **username_3** (2023-09-07): @MartinKealey: As described in my answer, there are situations where allowing values to behave as a non-deterministic union of possible states may facilitate useful optimizations that may be consistent with application requirements even if they're not consistent with deterministic sequential processing, but clang and gcc interpret UB as an invitation to behave in gratuitously nonsensical behavior which throws all laws of causality out the window.  
> **username_14** (2023-09-08): A C compiler is allowed to assume that undefined behaviour never happens. If follows that x>42 and y=5. It cannot be  anything else without undefined behaviour.  
> **username_3** (2023-09-08): @gnasher729: A C compiler which can correctly process a contrived and useless program that nominally exercises the translation limits in N1570 5.2.4.1 is allowed to behave in completely arbitrary fashion when given almost any other program.  The authors of the Standard have stated in the Rationale that they expected quality implementations to behave usefully in more cases than mandated by the Standard, though they waived jurisdiction over what cases those would be.  
> **username_15** (2023-09-09): I'm pretty sure the lattice you are describing is upside down from the usual way it's presented (doesn't really matter, but it is interesting)  
---
### Answer by username_16 on 2023-09-06. Score: 22
Many instances of undefined behaviour in the C specification represent accommodations for existing implementations where the "obvious" treatment was not possible, including detecting and diagnosing it. For example, reading a pointer to uninitialised memory may trigger memory-mapped effects on some architectures, beyond merely producing an unspecified result, or local variables may run into hardware guards on register access. These are exactly the "nasal demons" scenarios where **effects outside the language can occur**. These platforms are less common today, though many embedded architectures do have unusual behaviours to someone accustomed to x86 or ARM.

A language that wishes to compile directly to these platforms, or a specification for a language with existing implementations on them, will need to address these situations. The C specification is in this category, and encoded the commonality of what existed, not a from-scratch definition of what ought to be. It gives some minimum portable guarantees, but also accommodated the (possibly bad) choices made already.

A *new* language probably doesn't need to have any of this. These sorts of systems may be irrelevant enough to rule out, or it may be able to define its memory model sufficiently to avoid this coming up, requiring implementations for those targets to do extra work to accommodate it. It may just make uninitialised access inexpressible at the language level. Given the much-reduced compatibility costs, it would probably be the right choice.
> **Comments:**
> **username_11** (2023-09-06): Huh.  Guess we both had the same thought at the same time... :)  
> **username_3** (2023-09-07): I don't think there was ever really any doubt as to how an implementation for the Apple II should be expected to behave on a typical machine if code attempts to read-dereference a character pointer whose bit representation matches integer value 0xC0E9.  The compiler may have no idea what that would do (beyond issuing a read of address $C0E9), but someone adequately familiar with the hardware should recognize that such an action would turn on the drive-motor control for a floppy controller in slot 6, but "modern" UB has ceased to be a hardware issue.  
---
### Answer by username_11 on 2023-09-06. Score: 13
I am not sure if this was in the C standardisation committee's mind when making this decision, but it is worth noting that some computers may perform arbitrary actions when memory is read. A memory read is generally translated by the processor into a signal on a bus that is connected to that processor, and devices connected to the bus may behave however they wish in conjunction with that. Thus reading from an arbitrary address may have arbitrary effects.

One real-world example is the Apple II computer, which had various addresses that changed system behaviour when read from, which were typically called "soft switches". Reading from addresses in the range 0xC010-0xC017 will cause various sections of memory to be paged in and out (potentially including the memory containing the code being executed). Reading from addresses between 0xC018-0xC01F will change display modes, etc.

A more modern example might be memory-mapped registers in various microcontrollers, for example in AVR microcontrollers such as the [ATtiny402](https://ww1.microchip.com/downloads/aemDocuments/documents/MCU08/ProductDocuments/DataSheets/ATtiny202-204-402-404-406-DataSheet-DS40002318A.pdf) there are a number of addresses that correspond to the low byte of a 16-bit register. When these are read, a different memory address's value (the `TEMP` register) changes to the current value of the high byte of the same register, thus allowing two different 8-bit read operations to reliably access a 16-bit value that may change asynchronously.

I'm not sure what the best way the C specification could have used to specify that this kind of behaviour could happen, but it is important to note that memory reads are not necessarily entirely without side effects.
> **Comments:**
> **username_5** (2023-09-06): This is a good point, but it applies to reading from arbitrary memory addresses ─ as opposed to known addresses of memory which isn't initialised *yet*, like uninitialised local variables. Presumably a local variable would not be stored in one of these special addresses which has side-effects when read.  
> **username_11** (2023-09-06): True.  It's been a while since I read the C standard (and there have been a number of updates to it since then), but IIRC it does not distinguish between these categories, This may be an intentional simplification in order to keep the standard manageable.  
> **username_6** (2023-09-06): The undefined behavior only applies when reading "an object of automatic storage duration that could have been declared with the register storage class (never had its address taken), and that object is uninitialized" (C11 spec 6.3.2.1.2) -- a local var -- so would not apply to global things like memory-mapped I/O (which would necessarily be implementation-dependent)  
> **username_12** (2023-09-06): Just as a comment on this, the C committee (and, indeed, K&R themselves) did think of memory-mapped I/O. That is what "volatile" is for.  
> **username_3** (2023-09-07): A more interesting address on the Apple II would be 0xC0EF.  Reading that address within about a second of a disk access via controller in slot 6 will likely overwrite a track of data on the last accessed drive.  
---
### Answer by username_13 on 2023-09-07. Score: 9
> For what purpose would a language designer declare any uninitialized memory access undefined behavior (program behavior is meaningless) as opposed to merely unspecified (the value contained could be one of any number of possible values unpredictably)

A big question is how you obtain a pointer to the uninitialized memory.

The simplest case is an uninitialized stack variable. Modern systems allocate stack page-by-page on demand. Every system I know does so on any access, but it is conceivable that some system could do it only on first write. That could even be useful to catch some out-of-bounds read errors, and throw a segmentation fault.

The way C standard is written, it avoids over-defining behaviors when there is no useful purpose for them. It's hardly more useful to read an unspecified value than to potentially crash, thus it is not defined.

Note that this does not limit implementations: any implementation can define that access to uninitialized stack variable successfully reads unspecified value. If the implementation does stack scrubbing, it could even specify that uninitialized stack variables read out as zeros. Sadly, many implementations do not have good documentation on such behaviors.
> **Comments:**
> **username_3** (2023-09-07): //The way C standard is written, it avoids over-defining behaviors when there is no useful purpose for them. // It goes further than that, and goes out of its way to ensure that if a useful optimizing transform might yield program behavior that's in any way observably different from the original, at least one action in that execution will be classified as UB, even if all individual actions within the program would otherwise have fully defined behavior.  
---
### Answer by username_19 on 2023-09-07. Score: 8
**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.
> **Comments:**
> **username_5** (2023-09-07): I find this argument unconvincing. Making it a compiler error or runtime error would be the best way to ensure that nobody thinks it's intentional. If the goal is for bugs to be more visible, then it is perverse to give the compiler permission to transform a mistake into arbitrary behaviour and not notify the programmer that it has done so. That arbitrary behaviour might be only very subtly or very rarely different to what was actually intended, and then the bug goes unnoticed for a long time.  
> **username_3** (2023-09-07): @kaya3: More generally, there are many situations where some tasks will be facilitated by an ability to do some things, but other tasks would never deliberately do such things and it would be useful to trap if a program attempts them.  What would be most helpful would be recognizing that such actions may be incompatible with some useful diagnostic tools, and questions about the relative usefulness of the actions versus the tools should be left up to programmers.  Any attempted "one-size-fits-all" compromise will end up doing a bad job of fitting conflicting needs.  
> **username_3** (2023-09-07): How does characterizing an action as resulting from a "non-portable or erroneous program construct", and waiving jurisdiction over it, imply any judgment that the behavior could not have been intended?  
> **username_19** (2023-09-08): @kaya3: It's not about perversion, it's about humility and pragmatism. Not all kinds of bugs are easily detected at compile-time, or even at run-time. Not all kinds of bugs have easy remediation. Use of uninitialized memory, like use-after-free, belongs to the most difficult to diagnose bugs. ASan (Address Sanitizer) which detects some of those has non-trivial run-time overhead. If you can't efficiently catch all occurrences of such an error, then you have a hard question in front of you.  
> **username_19** (2023-09-08): @supercat: It seems obvious to me. If neither the standard nor the compiler ("implementation") you use define the behavior, then what can you expect from it? Nothing and Everything. If the mere presence of the construct completely voids any purpose of your program, then it's either an error or art... and nobody cares about diagnosis for the latter.  
> **username_3** (2023-09-08): @MatthieuM.: Which is a simpler rule: reading an object whose storage holds a valid bit pattern always yields the value associated with that bit pattern, or [as above] only if other conditions apply?  If a program is designed in such a way that the former described action would satisfy application requirements for every possible bit pattern a region of storage could hold, the program may be "non-portable" to implementations where it would be possible for storage to hold an invalid bit pattern, but that would not imply that it wasn't 100% correct on platforms where all bit patterns were valid.  
> **username_19** (2023-09-08): @supercat: I don't care for simplicity as much as I care for correctness. I'd rather get a diagnosis -- even, begrudgingly, a run-time diagnosis when running under a validator -- than let garbage data flow freely into the computations. As Babbage said: Garbage In, Garbage Out.  
> **username_3** (2023-09-08): @MatthieuM.: If C included a construct that meant "treat some range of storage as having been written with whatever byte pattern it happens to contain", then it would be reasonable to deprecate code that relies upon such behavior without using such a construct.  Further, a good standard should make clear that "diagnostic" implementations may trap on *any* action a programmer knows code isn't doing deliberately, without regard for whether the behavior of the action would otherwise be defined.  As an analogy, a tool which on program exit reports any storage that has been malloc'ed but not...  
> **username_3** (2023-09-08): ...released may be far more useful with a program that releases all storage before it exits than one which skips such actions, but that doesn't mean a program that exits without first taking the time to release memory should be viewed as "defective".  The fact that some constructs would undermine the usefulness of some diagnostic tools doesn't mean they should be universally forbidden, but merely viewed as something programmers should factor into their judgments about how to do things.  
> **username_17** (2024-05-08): "Making it a compiler error or runtime error" often requires runtime checks that are likely to have a prohibitive effect on performance (there are cases where the compiler can detect it statically, but this is usually impossible when pointers or conditionals are involved). When it can be detected statically, IDEs now will warn.  
---
### Answer by username_17 on 2023-09-07. Score: 6
If nothing else, it simplifies the language specification, and as a consequence makes it easier for programmers to understand the language, and also makes things easier for implementors.

As you say, some types may have no trap representations, but others might. But distinguishing access to uninitialized variables based on the type adds complexity to the specification. Programmers will have to take note of the types of their variables when taking advantage of being allowed to access them. Compiler writers will need different optimization code depending on the variable types.

And what do we gain from this added complexity? Not much. There's rarely an important reason to access uninitialized memory. An exception might be something like assigning a struct that has some uninitialized members, or `memcpy()` a buffer that may only have a portion filled in (although the application should usually know where the initialized portion ends and can use that as the copy size).

In practice, nothing bad is actually likely to happen from these accesses. While it may be UB to use `memcpy()` when some portion of the copied memory is uninitialized, it's not UB to use `realloc()` on a pointer to uninitialized data. Yet `realloc()` effectively has to do the same `memcpy()` when it needs to relocate the allocation -- there are few architectures where `realloc()` can tell which portions are allocated.
> **Comments:**
> **username_3** (2023-09-07): Most of the useful optimizing transforms that would be facilitated by treating uninitialized storage with semantics looser than "Unspecified Value" could only be performed in programs that would sometimes read uninitialized storage in cases where the read value wouldn't matter.  If programmers have to ensure that all storage is written in able to be able to perform any meaningful tasks, nothing will be gained by facilitating such optimizations, meaning that the rules allowing them would have only negative effects.  
> **username_17** (2023-09-07): I'm not sure if you're supporting or contradicting me. :)  
> **username_3** (2023-09-07): The benefits from allowing programmers to access uninitialized storage in situations where all bit patterns would equally satisfy application requirements may not be particularly huge, but they're greater than the performance benefits from allowing compilers to behave in completely arbitrary fashion if such accesses occur.  
---
### Answer by username_18 on 2023-09-08. Score: 4
Jens Gustedt, who is involved in the C standardization, has addressed this [in an answer on SO](https://stackoverflow.com/a/11965368/3150802).

The executive summary:

**Reading uninitialized memory is not *necessarily* undefined behavior in C.**

There are exactly two things that can go wrong.

1. The type has trap representations, and that happens to be the representation in that memory location;
2. An object with automatic storage duration (i.e., a local variable or a parameter) may be held in a register. The reason this is UB is that there are platforms which have special provisions to prevent read before write from registers, as an error detection.

UB is restricted to these two possibilities. We know that there are no trap representations. If your variable does not have automatic storage duration, or if you take its address — which prevents it from being in a register — you are safe.
> **Comments:**
> **username_3** (2023-09-11): The notion that things depend on whether things "can" be kept on registers fails Interestingly; gcc-ARM used to process uninitialized automatic-duration objects of type `uint16_t` in a manner inconsistent with any bit pattern such objects could hold, even in cases where their addresses were taken, and has fixed that behavior by unconditionally initializing the associated registers to zero even in cases where their addresses aren't taken.  
---
### Answer by username_1 on 2023-09-07. Score: 4
The "as if" rule means that a variable need not have a fixed address in memory unless you take its address (and keep that in a pointer variable somewhere else).
This can be used by optimizers to store the "current" value of a single variable in a succession of different locations, including multiple registers, and maybe even multiple memory addresses (*1).
So when some branch of the code causes the compiler to ask its register/variable tracker "where is the value of this variable", and the data flow analysis proves that it's uninitialised, the tracker isn't allowed to say "dunno" (because that would abort the compilation), so it may simply choose a random register and say "in here".
Even if it always picks the same dummy register, it might also get used for something else, so the apparent value of the variable isn't just "unspecified", it's random every time it's read.
For example, given this:
extern int foo();
void func() {
  int a;
  if (a && foo() && !a)
    puts("a is uninitialised");
}

clearly foo can't see a, nor intentionally alter it, and yet the apparent value of a could change because foo is compiled to something that uses the register that also gets used when a is read. Since a doesn't "own" the register, it needn't be saved while foo is called.
At the same time, the compiler could look at a && ... !a and decide that that can "never happen", and remove the code for the puts statement entirely. So when foo is called and comes back with true, there are random bytes where the program code should be. Then we get to watch the parade of nasal daemons.
(*1: Some CPU's can do PUSH and POP faster than general memory writing & reading, and the compiler could take advantage of that when it needs to spill registers, rather than always writing to locations based on which variable the register currently holds.)
> **Comments:**
> **username_3** (2023-09-07): An abstraction model that recognized the possibility of indeterminate or even non-deterministic values, but limited the range of possible actions to those that were within the set of actual possibilities (so that evaluating e.g. `(x & 3)+(x & 9)` when `x` is uninitialized might yield 0, 1, 2, 3, 4, 8, 9, 10, 11, or 12, but those would be the only possible behaviors) would allow more useful optimizations than one that requires that programmers add otherwise-unnecessary code that would make such optimizations irrelevant.  
> **username_1** (2023-09-14): @supercat Interesting that odd numbers are among the suggested results, indicating that `x` might not give the same value twice. Another real possibility is that an uninitialised variable could result in a trap when read. This really did happen on the Intel IA64, where registers had an extra `NAT` _Not A Thing_ bit.  
> **username_3** (2023-09-14): If a value is read multiple times, the reads might yield different values.  Another possible source of weirdness I meant to mention in my answer (tell me if you think it could fit in my answer, or might be better as another answer) is that if a value smaller than a register is optimized into a register, reading it might yield the contents of that register whether or not it's within range of the value.  IMHO, a good abstraction model for general-purpose implementations should allow for this possibility, but *only* if a compiler refrains from making its own downstream assumptions...  
> **username_3** (2023-09-14): ...about the range of possible values after the value is copied to some other object.  Given e.g. `unsigned char x; int y; ... y=x; if (y < 256) do_something(y);` a compiler should have the choice of either processing `y=x;` in a way that would set `y` to a value 0..255 even if `x` was unintialized and then omit the `if` check, or processing it in a way that might set `y` to a value over 255 but then skip the `do_something()` call, but a general-purpose compiler should only be allowed to assume `y` will be less than 256 if its generated code would make `y` less than 256.  
---
### Answer by username_3 on 2023-09-07. Score: 2
The only way in which the C Standard can accommodate situations where optimizing transforms may yield program behavior that's observably inconsistent with sequentially executing a program as described by a platform's abstraction model, is to categorize as Undefined Behavior at least one action performed in a program execution where that may occur.  This makes it necessary to classify as Undefined Behavior actions whose behavior might be affected by optimization, even if the original and transformed behaviors would have satisfied application requirements.
Consider the following pair of functions:
struct blob { uint32_t ct; uint8_t dat[28]; } x,y;
void test1(void)
{
  struct blob temp;
  temp.ct = 3;
  temp.dat[0] = 10;
  temp.dat[1] = 20;
  temp.dat[2] = 30;
  x = temp;
  y = temp;
}
void test2(void)
{
  test1();
  fwrite(&x, 32, 1, someFile);
  fwrite(&y, 32, 1, someFile);
}

What should or shouldn't be guaranteed about the two blobs written to the file?
If nothing in the universe would care about the contents of the last 25 bytes of each object, the most efficient machine code implementation of test() that would satisfy application requirements would probably write the count field and first four bytes of data into both x and y, while leaving the other parts of those structures unmodified (though it might in some cases be faster to only write the first seven bytes of the structure).  The C Standard, however, would have no way of allowing the last 25 bytes of each record from being written differently without allowing implementations to behave in completely arbitrary fashion.
> **Comments:**
> **username_4** (2023-09-08): I see a definition for `test1` and for `test2`, but no definition for `test`, which is called by `test2`.  Am I missing something?  
> **username_3** (2023-09-08): @Brian: The `test2()` function was supposed to be calling `test1()`.  Code has been corrected.  The split between `test1()` and `test2()` was intended to make the point that a compiler processing code `test1()` may have no way of knowing what if anything downstream code would be doing with `x` and `y`, and the purpose of using `fwrite` was that it should be capable of writing without weird side effects any bit pattern that might be stored at the source address, and a compiler would have no way of knowing whether anything in the universe would care about the contents of any particular byte.  
---
