Undefined behavior in C is a reading error
It&#x27;s easy to pick on undefined behavior in C when you focus on the more gratuitous undefined behaviors such as signed overflow or oversized shifts. I&#x27;m not certain why these are undefined behavior instead of implementation-defined, but my suspicion is that these caused traps on some processors, and traps are inherently undefined behavior.<p>Instead, if you dislike undefined behavior, I challenge you to come up with workable semantics for non-gratuitous scenarios, of which I&#x27;ll lay out three. Remember, if it&#x27;s not undefined, then there are some defined semantics that need to be preserved, even if implementation-defined, so you should be able to explain those semantics. If you can&#x27;t find such semantics, then maybe undefined behavior <i>isn&#x27;t</i> such a bad thing after all.<p>The first case is the compiler hint. A function marked _Noreturn returns. Or you alias two pointers marked restrict. Remember that the entire point of these hints is to permit the compiler to not generate code to check for these scenarios.<p>The second case is uninitialized memory. You&#x27;ve probably already come up with such semantics, so I&#x27;ll point out an optimization scenario that you probably don&#x27;t object to that your semantics didn&#x27;t cover:<p><pre><code>  static int y; &#x2F;* uninitialized *&#x2F;
  _Bool cond = f();
  int x = cond ? 2 : y; &#x2F;* Is it legal to fold this to int x = 2; ? *&#x2F;
</code></pre>
Hopefully, you&#x27;ll agree that that is a reasonable optimization. Now consider this code:<p><pre><code>  static int y; &#x2F;* uninitialized *&#x2F;
  _Bool cond1 = f(), cond2 = g();
  int x1 = cond1 ? 2 : y; &#x2F;* So int x1 = 2; *&#x2F;
  int x2 = cond2 ? 3 : y; &#x2F;* So int x2 = 3; *&#x2F;
  if (!cond1 &amp;&amp; !cond2) {
    assert(x1 == x2); &#x2F;* Uh... *&#x2F;
  }
</code></pre>
This is just scraping the tip of the surface of uninitialized values. Developing sane semantics around these sorts of values that also allow reasonable optimizations is challenging. You can look at the very lengthy saga that is undef, poison, and freeze in LLVM (still ongoing!) to see what it looks like in practice.<p>The third category is traps. Let&#x27;s pick an easy example: what happens if you dereference a null pointer? Now let&#x27;s consider the consequences of those semantics on some more code examples:<p><pre><code>  int *x = NULL;
  int *y = &amp;*x; &#x2F;* Can this be lowered to int *y = x; ? *&#x2F;
  size_t offset = (size_t)&amp;((struct foo *)NULL)-&gt;field; &#x2F;* Remember, x-&gt;a is actually (*x).a *&#x2F;
  int *z = get_pointer();
  *z; &#x2F;* No one uses the result of the load, can I delete it? *&#x2F;
  for (int i = 0; i &lt; N; i++) {
    foo(*z); &#x2F;* Can I hoist the load out of the loop? *&#x2F;
  }
</code></pre>
Note that all of the optimizations I&#x27;m alluding to here are ones that would have existed all the way back in the 1980s when C was being standardized, and these are pretty basic, pedestrian optimizations that you will cover in Compilers 101.