<p>Some language specifications invite compilers to make certain assumptions, and behave in completely arbitrary fashion if such functions are violated, even if the code in question would have unambiguously defined behavior in the absence of such invitation and assumption.  This is done to uphold the principle that the behavior of any defined program execution must be consistent with all operations having been performed in the specified sequence.</p>
<p>It would also be possible, however, for a language specification to transform various optimizing transforms when certain conditions apply, even when they would observably affect program behavior, and for it to invite compilers to make certain assumptions <em>for the purpose of deciding when certain transforms may be applied</em>, but limiting their behavior in cases where the assumptions are violated to those which could result from performing the indicated transforms in a manner which is <em>agnostic</em> to whether the assumptions actually hold true.</p>
<p>Consider, for example, the following two ways of specifing the meaning of a &quot;pure&quot; function qualifier:</p>
<ol>
<li><p>A compiler may assume that no pure function will have any side effects, and generate code that behaves in arbitrary function if this assumption is violated.</p>
</li>
<li><p>If a program has received input which--for some combination of unspecified behaviors--could result in a function being invoked with certain argument values without any additional input being received, the generated code may invoke the function any number of times, whenever the compiler sees fit (possibly skipping all invocations if a compiler can determine the return value via other means).  <em>For purposes of determining whether a function will be invoked with particular argument values</em>, a compiler may assume that all pure functions will return to their caller, and all loops which have a single statically-reachable exit will terminate.</p>
</li>
</ol>
<p>If program behavior could be observably affected by a compiler's choice of when to invoke pure functions, but all behaviors that could result from any combination of choices would be equally acceptable, the second specification would make it easy for programmers to invite compilers to adjust the calling seqeunce in whatever manner would be most efficient.  Under the first behavioral model, however, programmers would be required to avoid pure qualifiers on any function that might--in any builds--need to have some side effects, even if the only side effects would be the generation of log entries indicating how the functions were called.</p>
<p>What advantages are there to the currently-popular approach of characterizing all executions as either fully specified or &quot;anything can happen&quot; UB?</p>
<p><strong>ADDENDUM</strong></p>
<p>To offer a more concrete example of a situation that arises using standard C syntax, consider the function:</p>
<pre><code>char arr[65538];
unsigned test1(unsigned x)
{
  unsigned i=1;
  while ((unsigned short)i != x)
    i *= 3;
  if (x &lt; 65536)
    arr[x] = 1;
  return i;
}
</code></pre>
<p>I can see three ways a language specification could address scenarios where <code>x</code> would exceed 65535, but the return value of the function would be ignored:</p>
<ol>
<li><p>Specify that the loop must execute as written, preventing any subsequent code from executing.  If a compiler does this, it could consolidate the <code>if</code> test with the loop test, effectively performing an unconditional assignment which is sequenced after the loop exit.</p>
</li>
<li><p>Specifying that because there are no dependencies between any values that are computed in the loop and any operations that occur after it, the program may be processed as though the loop didn't exist, which would require that the <code>if</code> condition be evaluated.</p>
</li>
<li><p>Specifying that because the loop would fail to terminate in such cases, a compiler may generate code that behaves in arbitrary fashion, including storing 1 to <code>arr[x]</code> without regard for whether <code>x</code> is less than 65537.</p>
</li>
</ol>
<p>If <code>test1</code> is called by code that ignores the return value, both the clang and gcc C++ compilers will aggressively seek to replace the call with an unconditional assignment to <code>arr[x]</code>, essentially causing the execution of a side-effect-free loop to trigger an arbitrary (likely memory-corrupting) side effect.</p>

<optimization><undefined-behavior>
Pros and cons of "anything-can-happen" UB versus allowing particular deviations from sequential program execution
CC BY-SA 4.0
2023-07-17T02:51:16.397Z
Does this answer your question? [Why would a language have a concept of undefined behavior instead of raising an error?](https://langdev.stackexchange.com/questions/481/why-would-a-language-have-a-concept-of-undefined-behavior-instead-of-raising-an)
Bbrk24
CC BY-SA 4.0
2023-07-18T01:16:15.537Z
I don't know the answer to your question, but it reminds me of this 2014 blog post from Raymond Chen that you might find interesting. https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=633
Eric Lippert
CC BY-SA 4.0
2023-07-18T21:24:39.863Z
@Bbrk24: The linked suggested duplicate does not answer the question, since it addresses the question of why *some* actions must be UB, rather than the question of whether adding language rules which would classify as UB *actions which would be defined in those rules' absence" is as useful for optimization as much as other safer ways of facilitating optimization.
supercat
CC BY-SA 4.0
2023-07-19T15:21:51.733Z
**Comments have been [moved to chat](https://chat.stackexchange.com/rooms/147332/discussion-on-question-by-supercat-pros-and-cons-of-anything-can-happen-ub-ver); please do not continue the discussion here.** Before posting a comment below this one, please review the [purposes of comments](/help/privileges/comment). Comments that do not request clarification or suggest improvements usually belong as an [answer](/help/how-to-answer), on [meta], or in [chat]. Comments continuing discussion may be removed.
Alexis King
<blockquote>
<p>It would also be possible, however, for a language
specification to transform various optimizing transforms
when certain conditions apply, even when they would
<strong>observably affect program behavior</strong></p>
</blockquote>
<p>I would find it to be very unexpected and confusing if my compiler did something that altered the way my program behaves.  I expect it to do optimizations, but the overall behavior remains as written.  Many languages make that an explicit requirement for the compiler.  A compiler that alters program behavior means that code is much more difficult to write and debug, even if all such transformations are well-defined.</p>
<blockquote>
<p>for it to invite compilers to make certain assumptions for
the purpose of deciding when certain transforms may be applied,
but limiting their behavior in cases where the assumptions are
violated to those which could result from performing the
indicated transforms in a manner which is agnostic to whether the
assumptions actually hold true.</p>
</blockquote>
<p>You might be able to do something like this for select cases, but not in general.  Much of what is labeled &quot;undefined behavior&quot; are things that neither the language nor the compiler can control in the first place.  You can tell a compiler that it can assume all loads will be made from properly-aligned addresses.  You <em>can't</em>, however, tell it what the behavior should be when that assumption is violated. That behavior varies based on the system architecture, and the compiler cannot control that. You can't reasonably specify a &quot;manner agnostic to whether the assumptions are true&quot; because there's no guarantee that such a thing even <em>exists</em> on all platforms.</p>
<blockquote>
<p>If program behavior could be observably affected by a
compiler's choice of when to invoke pure functions, but all
behaviors that could result from any combination of choices
would be equally acceptable</p>
</blockquote>
<p>You are asking the compiler to make a judgement call.  Whether an outcome is &quot;acceptable&quot; is something only the programmer can decide.  Maybe those loads are reading from memory-mapped pointers that have a side effect when they're read.  Maybe the code is specifically written in a less-than-efficient way to guarantee consistent execution time to avoid timing attacks.  You really don't want the compiler to start trying to decide whether something is or isn't &quot;acceptable&quot;.  Intelligent humans have a hard enough time with that.</p>
<blockquote>
<p>What advantages are there to the currently-popular approach
of characterizing all executions as either fully specified
or &quot;anything can happen&quot; UB?</p>
</blockquote>
<p>Those are really the only two options. Either something is defined, or it isn't. If it's undefined, then &quot;anything can happen&quot; is true by definition.  Note that doesn't mean that you're giving the compiler license to do anything that it wants to do. It simply warns the programmer that the spec alone can't predict what will happen when that code is executed so it's best to assume the worst.  As a simple example, the code emitted for <code>x &lt;&lt; y</code> is undefined when <code>y</code> is negative.  The compiler still has to generate code that works for valid values of <code>y</code>, but there's no telling what that same code will do when <code>y</code> is negative.  C doesn't have a way of reporting error conditions for operators, so they get called &quot;undefined behavior&quot; to let the programmer know that it's <em>their</em> responsibility to check these things.</p>
<p>That being said, I think you're forgetting the way that this normally plays out in practice.  Many things classified as UB set boundaries for the language and its usage.  When programmers wander off into UB territory, it's usually either unintentional (a bug) or they're doing something they shouldn't be doing in the first place (writing awful code).  I scanned through the list of Undefined Behavior in section A.6.2 of the C89 spec, and the types of things listed there are cases where things have really run off the rails.  When a compiler sees those in a program it doesn't result in &quot;anything goes&quot; output code, it gets flagged as an error.  Undefined Behavior means the compiler doesn't know what it should do.  In practically all of these cases, the correct response is to bail out and gripe at the user.  Compilers are supposed to be picky.</p>
<p>Regarding your sample code:</p>
<blockquote>
<ol>
<li>Specify that the loop must execute as written, preventing
any subsequent code from executing. If a compiler does this, it
could consolidate the <code>if</code> test with the loop test, effectively
performing an unconditional assignment which is sequenced after
the loop exit.</li>
</ol>
</blockquote>
<p>It seems unlikely that a compiler would do this since the only way to know that this is possible is to determine that the loop never terminates.  If that's the case, then the code following it doesn't matter.  Therefore, there's no advantage in doing the extra work to optimize the effectively-dead code. Just keep things simple and either emit the code as-is with no optimizations, or delete it all.  Selectively deleting only <em>part</em> of your dead code just means more work for the compiler writer for no benefit.</p>
<blockquote>
<ol start="2">
<li>Specifying that because there are no dependencies between
any values that are computed in the loop and any operations that
occur after it, the program may be processed as though the loop
didn't exist, which would require that the if condition be evaluated.</li>
</ol>
</blockquote>
<p><code>gcc</code> seems to do this, at least according to the documentation (it's mentioned in section 14.8).  Optimizations are outside the spec of the language, but the compiler should document this behavior.</p>
<blockquote>
<ol start="3">
<li>Specifying that because the loop would fail to terminate in
such cases, a compiler may generate code that behaves in
arbitrary fashion, including storing 1 to arr[x] without regard
for whether x is less than 65537.</li>
</ol>
</blockquote>
<p>There's a huge difference between &quot;we can't predict exactly what a piece of code will do&quot; (as in what happens when you overrun an array) and &quot;I'm just going to wing it and generate whatever code I feel like generating&quot;. The compiler should generate code that matches the functionality of what the programmer wrote.  Sometimes that code may not behave predictably.  That's not the compiler's fault.  Eliminating the <code>if</code> would only make sense in cases where the function was inlined and it was called with a constant argument that was less than 65536.  For larger input values or where the <code>if</code> can't be evaluated at compile time, an unconditional assignment would be incorrect.</p>
<blockquote>
<p>If <code>test1</code> is called by code that ignores the return value, both
the clang and gcc C++ compilers will aggressively seek to replace
the call with an unconditional assignment to <code>arr[x]</code>, essentially
causing the execution of a side-effect-free loop to trigger an
arbitrary (likely memory-corrupting) side effect.</p>
</blockquote>
<p>In this particular case, it doesn't feel like the result you describe is intentional.  Removing an array bounds check shouldn't be done unless you can be absolutely certain that it'll never fail.  It should be obvious to the compiler that the <code>if</code> in this code is a bounds check for the following line.  This really feels like you have the optimizations cranked way up, and there's a problem with the way several of the optimizers and the inliner are interacting (eliminating the loop and <code>i</code> is understandable, but <code>x</code> and the bounds check is not).  I recommend asking about this on the gcc or clang mailing lists.</p>

CC BY-SA 4.0
2023-07-18T23:46:40.800Z
2023-07-18T23:46:40.800Z
bta
CC BY-SA 4.0
2023-07-19T14:52:15.423Z
The rules of c++ allow loops to be optimized according to #3, *and that's precisely what clang and gcc actually do*.  Since the code after the loop would be unreachable if `x` were greater than 65535, they eliminate the `if`, and if the value of `i` is never observed they'll eliminate the loop as well.  I think optimizers embracing such nonsense should be recognized as unsuitable for any programs that might receive potentially malicious input, the maintainers of clang and gcc claim people want compilers to perform such "optimizations".
supercat
CC BY-SA 4.0
2023-07-19T17:04:21.287Z
//You can't reasonably specify a "manner agnostic to whether the assumptions are true" because there's no guarantee that such a thing even exists on all platforms.// Many programs are not written for abstract machines, but for real computers, and in many cases programmers may know things about the target computers that the implementation can't.  If the programmer knows that a program will only run on platforms where a read of an arbitrary address will never have any effect beyond yielding a possibly-meaningless value, machine code that exploits this may be more efficient than...
supercat
CC BY-SA 4.0
2023-07-19T17:08:48.980Z
...machine code that avoids out-of-bounds reads.  A programmer targeting a compiler that processes reads in a fashion which whose effects will always be limited to performing an actual read with whatever consequences result and/or yielding a possibly meaningless value, and a platform which is known to process reads in side-effect-free fashion, could allow such a compiler to generate more efficient code than would be possible if the source code were written to prevent out-of-bounds reads at all costs.
supercat
CC BY-SA 4.0
2023-07-19T17:12:20.183Z
//When programmers wander off into UB territory, it's usually either unintentional (a bug) or they're doing something they shouldn't be doing in the first place (writing awful code).// Why do you suppose the Standard describes UB as resulting from "non-portable or erroneous program constructs...", rather than "program constructs which are erroneous (rendering portability irrelevant)"?  C's reputation for speed came about because it let programmers exploit constructs which their intended target platforms were known to process usefully.  Code exploiting such constructs would only be portable...
supercat
CC BY-SA 4.0
2023-07-19T17:14:16.367Z
...among platforms that processed those constructs in expected fashion, but there was no intention to require that programmers subject themselves to limitations that would be imposed by *platforms upon which code would never be executed*.
supercat
CC BY-SA 4.0
2023-07-19T17:23:10.973Z
//You are asking the compiler to make a judgement call. Whether an outcome is "acceptable" is something only the programmer can decide.// Indeed, and a programmer that would regard variety of behaviors as acceptable, but some other behaviors as unacceptable, would *tell* the compiler that by writing the code in a manner that would allow a compiler to choose freely among those the programmer viewed as acceptable, but not include among the allowable behaviors any that the programmer would regard as unacceptable.
supercat
CC BY-SA 4.0
2023-11-10T17:16:07.383Z
@8bittree: A fundamental difference between "Implementation Defined" behavior and UB is that any action whose behavior might conceivably be affected by optimizing transforms in any observable way must be classified as UB, even if the transform would replace one behavior meeting application requirements with a different behavior that would--despite being different--still satisfy many application's requirements.
supercat
CC BY-SA 4.0
2024-07-10T14:33:47.560Z
@8bittree: I should have further clarified that between saying that "`int1=x*30/15`" will process the multiply using precise two's-complement semantics" and `int1=x*30/15` may behave in arbitrary fashion if `x` is larger than `INT_MAX/30`, would be a third possibility of saying that when `x` exceeds `INT_MAX/30`, `int1` may be assigned any number *without side effects*.  I've come to realize that present compiler designs are ill equipped to handle the notion that a compiler could *either* replace the code with `int1=x*2` *or* use truncating two's-complement multiply and exploit...
supercat
CC BY-SA 4.0
2024-07-10T14:38:31.410Z
...the fact that `int1` will always be in the range `INT_MIN/15` to `INT_MAX/30`, but that a compiler could only exploit the latter post condition if it *actually generated code* that would guarantee it.  Much easier to write language rules in such a way that forbids programmers from writing code that would allow compilers a choice of optimizations that would be fine individually, but must be treated as mutaully exclusive.
supercat
CC BY-SA 4.0
2023-08-03T17:42:56.840Z
@bta There is a 3rd option between Fully Specified and "Anything can happen" UB: *Implementation Defined*. You won't find the exact details of the behavior in the language spec, instead you'll have to look at your implementation's documentation. Obviously, this loses portability, but it does offer consistent, reliable behavior. Implementation A might say a left shift with a negative number turns into a right shift by the absolute value and implementation B might say it's a no-op, but neither one will spontaneously summon nasal demons if they don't say they will (bugs aside).
8bittree
<p>If there are some <em>know or expected</em> particular deviations, then these deviations are <em>defined</em> somewhat. But the thing with UB is that it really means undefined, anything goes. See some example below.</p>
<p>Focusing entirely on the title and the last paragraph:</p>
<blockquote>
<p>What advantages are there to the currently-popular approach of characterizing all executions as either fully specified or &quot;anything can happen&quot; UB?</p>
</blockquote>
<p><a href="https://predr.ag/blog/falsehoods-programmers-believe-about-undefined-behavior/#:%7E:text=Falsehoods%20about%20the%20possible%20outcomes%20of%20UB" rel="nofollow noreferrer">UB is all or nothing.</a> If <em>one</em> very small and very local thing is <strike>undefined behavior</strike> anything can happen, then <em>all things can happen</em>, and nothing can be expected afterwards.</p>
<p>UB is contagious. UB cannot be contained. After it happens, <a href="https://blog.tchatzigiannakis.com/undefined-behavior-can-literally-erase-your-hard-disk/" rel="nofollow noreferrer">all your disks can be zero</a>. Code guarding against UB can and <a href="http://blog.pkh.me/p/37-gcc-undefined-behaviors-are-getting-wild.html" rel="nofollow noreferrer">probably will be deleted</a>. There is no profit in speculating otherwise.</p>
<p>Also, <a href="http://www.catb.org/jargon/html/N/nasal-demons.html" rel="nofollow noreferrer">nasal demons</a>.</p>
<hr />
<p>As for the pros/cons of letting UB happen, the principal reason is performance, and this can be <a href="https://cs.stanford.edu/%7Ezhihao/papers/apsys12.pdf" rel="nofollow noreferrer">measured</a>.</p>

CC BY-SA 4.0
2023-07-17T01:03:24.190Z
2023-07-17T20:08:52.190Z
2023-07-17T20:08:52.190Z
André L F S Bacci
CC BY-SA 4.0
2023-07-17T01:57:02.187Z
In what way would the specification #2 in the question not be superior to the former approach for declaring "pure" functions.  Present compilers may not be capable of usefully exploiting such semantics, but that's no reason why future languages shouldn't be designed better.
supercat
CC BY-SA 4.0
2023-07-17T09:15:12.307Z
I don't think this answer adds any valuable information. We already know that UB means anything can happen, the question is what the pros/cons are for *letting* this happen.
G. Sliepen
CC BY-SA 4.0
2023-07-17T15:21:31.940Z
@G.Sliepen: My point is that a well-written spec shouldn't say "anything can happen" in all situations where a useful optimizing transform might result in a program behaving in a manner which is inconsistent with sequential program execution *but may still meet application requirements*.  If a programmer writes `do { x=foo(1); y=bar(z); ...}` and both function calls generate logs but are *otherwise* side-effect free, having N executions of the loop result in one log entry for `bar` followed by N entries for `foo` would be inconsistent with sequential program execution, but ...
supercat
CC BY-SA 4.0
2023-07-17T15:25:29.590Z
...any combination of log entries for calls to `foo` and `bar` would satisfy application requirements equally well, granting a compiler the freedom to choose among many behaviors *that all satisfy application requirements* would be more useful than saying that the only way to guarantee anything is to explicitly force everything.
supercat
CC BY-SA 4.0
2023-07-17T18:17:51.167Z
@G.Sliepen You can't avoid “letting” this happen unless you can ensure that nothing slips through the cracks. In a language without memory safety, it's impossible to guarantee that nothing slips through the cracks.
Gilles 'SO- stop being evil'
CC BY-SA 4.0
2023-07-17T18:18:36.417Z
@supercat such a spec would make assumptions about the underlying platform that c doesn't provide. For example, there exist c platforms (without any optimisation) where a ub pointer dereference can result in a motor spinning up.
user1937198
CC BY-SA 4.0
2023-07-17T19:00:03.957Z
@user1937198: On an Apple II with a floppy controller in slot #6 (the usual place), reading address $C0E9 will turn on the floppy drive motor, and reading address $C0EF while the motor is running will trash whatever track is under the drive head. From a language perspective, however, one could specify a "low level C" dialect where read-dereferencing a `char volatile*` holding address $C0E9 would synchronize the abstract and physical machine states and then instruct the environment to perform a load from address $C0E9.  The language spec wouldn't care about what the environment...
supercat
CC BY-SA 4.0
2023-07-17T19:02:08.063Z
...would do when instructed to perform the load, and a programmer who didn't know how the environment would process the load would have no way of knowing what the consequences might be, but a programmer who knew what the environment would do in response to such a load instruction would be entitled to rely on that behavior.  An access not qualified `volatile` would be somewhat similar, except that accesses might sometimes be "cached", hoisted, deferred, and/or consolidated.
supercat
CC BY-SA 4.0
2023-07-17T19:10:35.490Z
@supercat now I have a pointer to a char array at $1000, and attempt to write out of bounds to index $B0EF, how do you stop that trashing the disk without runtime bounds checking?
user1937198
CC BY-SA 4.0
2023-07-17T19:15:13.840Z
Without memory safety,  you can't prevent arbitrary memory load/stores, and without constraints on memory mapped io, you can't stop arbitrary load/stores having arbitrary effects. And thus any language that does not have memory safety,  and does not place constraints on memory mapped io, must have nasal demon ub.
user1937198
CC BY-SA 4.0
2023-07-17T19:18:39.500Z
And there are other possibilities of escape.   Without WxE protection, an invalid memory write could modify code, which when executed would have behaviour dependent on the machine code of the system, and could do anything that is possible in machine code.
user1937198
CC BY-SA 4.0
2023-07-17T19:40:21.220Z
You can list the explicit transforms under which a write can be moved, but if a write can result in arbitrary behaviour when out of bounds, and that behaviour can be hoisted, then you are effectively allowing time traveling undefined behaviour. It is entirely possible for a hoisted out of bounds write to attempt to write to memory that would corrupt control flow between the ordinal write point and thehoist point. At which point anything can happen.
user1937198
CC BY-SA 4.0
2023-07-17T21:39:39.193Z
@user1937198: In typical low-level dialects of C, the array access would be processed as a load or store from/to address `$C0EF`; if the execution environment dopcuments the effect of such access, a low-level implementation would process it "in a documented manner characteristic of the environment".  The Standard shouldn't concern itself with what the environment's documented characteristic action would be, or whether it would be desirable or undesirable.
supercat
CC BY-SA 4.0
2023-07-17T21:44:54.413Z
@supercat and to do so is a) implementation defined, and b) either limits your ability to move around accesses, as effectively all accesses must be treated as volatile unless proven otherwise, as all accesses have the potential to break memory safety,  or you effectively dump the documentation of the optimizer as your implementation defined behaviour spec, and end up with a spec so complex no one understands it
user1937198
CC BY-SA 4.0
2023-07-17T21:47:48.077Z
The issue with undefined behaviour vs implementation defined behaviour is that undefined behaviour usually comes from at least a platform where there is a potential subset of that behaviour, where if executed, even with no transforms, the compiler can no longer guarantee the runtime state of the program at all.
user1937198
CC BY-SA 4.0
2023-07-17T21:52:38.593Z
You are escaping the abstract model, and a sufficiently detailed implementation spec would be the ISA spec and associated translation details, along with any optimisations. Consider the case of an out of bounds write of program code, the only way to define it is to include how the write translates to machine code, and what machine code is already present.
user1937198
CC BY-SA 4.0
2023-07-17T21:57:24.480Z
@user1937198: How does the Standard seek to characterize actions which most implementations would process in the same useful fashion, but where on a few implementations it would be expensive to guarantee any behavior consistent with sequential program execution, and no possible behavioral spec would be useful?  Classifying an action as "Implementation-Defined" means that all implementations must document an action consistent with the overall abstraction model.
supercat
CC BY-SA 4.0
2023-07-17T22:00:45.300Z
@user1937198: //either limits your ability to move around accesses, as effectively all accesses must be treated as volatile unless proven otherwise// I specifically allowed for the possibility that compilers may reorder memory accesses, subject to various rules.  Making decisions about whether memory accesses may be reordered based upon static aspects of program structure is vastly easier than trying to *reliably* make determinations based upon dynamic aspects of program execution.  If one says that programmers requiring that actions occur in a particular sequence must ensure...
supercat
CC BY-SA 4.0
2023-07-17T22:01:40.170Z
...that there is at least one rule that would force the accesses to occur in that sequence would seem much saner than having rules which define actions whose behavior is obviously meant to be defined as equivalent to actions whose behavior is expressly characterized as undefined.
supercat
CC BY-SA 4.0
2023-07-17T22:02:38.793Z
Due to the politics of the C/C++ standards, its usually an all or nothing of undefined vs implementation defined behaviour.
user1937198
CC BY-SA 4.0
2023-07-17T22:07:27.133Z
allowing reordering subject to rules is the problem. what if the write modifies the code under execution to such that on a specific microarchitecture it results in a jump to non-code between the original and reordered write. You now have an apparently arbitrary subset of the code not being executed, and something completely different being executed instead. And so rather than trying to cover all these cases, the standard just goes, if you break memory safety,  all bets are off.
user1937198
CC BY-SA 4.0
2023-07-17T22:16:27.943Z
**Comments have been [moved to chat](https://chat.stackexchange.com/rooms/147290/discussion-on-answer-by-andre-l-f-s-bacci-pros-and-cons-of-anything-can-happen); please do not continue the discussion here.** Before posting a comment below this one, please review the [purposes of comments](/help/privileges/comment). Comments that do not request clarification or suggest improvements usually belong as an [answer](/help/how-to-answer), on [meta], or in [chat]. Comments continuing discussion may be removed.
lyxal
CC BY-SA 4.0
2023-07-19T02:39:16.987Z
Counterexample: signed integer overflow in C.  The original purpose of making it UB was to let compilers convert it to the native machine instructions regardless of how the machine represented signed integers.  This produced a finite and rather small set of "undefined" behaviors.  But since it's "anything can happen"-UB rather than bounded UB, programmers needs to wrap any signed integer operations that might overflow with elaborate guard constructs to prevent the compiler from sabotaging them.
Mark
CC BY-SA 4.0
2023-07-20T21:16:40.990Z
@Mark: In most kinds of code, having a compiler transform a construct like `x*30/15` into `x*2` would offer significant performance benefits without breaking programs that could have been better written to indicate programmer intent, but because the effects of such a transform would be observable, the as-if rule would prevent the optimization unless the action is classified as "anything can happen" UB.
supercat
CC BY-SA 4.0
2023-07-20T22:35:05.353Z
@supercat, you can still perform that transform under "bounded" UB.  If the result of any signed-integer operation is required to still be a signed integer, leaving it unspecified as to *which* signed integer results from overflow, most useful optimizations are still possible.
Mark
CC BY-SA 4.0
2023-07-21T05:01:09.293Z
@Mark: One could specify things in such fashion, but the phrase "Implementation-Defined behavior" is reserved for things that implementations were expected to document in greater detail than that.
supercat
<p>As far as I know, there are only two advantages: <strong>performance</strong> and <strong>ease of writing compiler optimizations</strong>.</p>
<h2>Performance</h2>
<p>When compilers can make assumptions, they always make assumptions to improve performance. This is because compilers are judged on the performance of generated code.</p>
<p>In other words, the compiler that wins on <em>benchmarks</em> has an advantage.</p>
<p>One example of this is this construct in C:</p>
<pre><code>&amp;var-&gt;field
</code></pre>
<p>which takes the address of the field.</p>
<p>In C, dereferencing a NULL pointer is UB, and anything can happen. The compiler uses that assumption to compile that code to something like this:</p>
<pre><code>value_of_var + offset_of_field
</code></pre>
<p>Without that bit of UB, that might need to do a check for NULL first.</p>
<h2>Ease of Writing a Compiler</h2>
<p>Put yourself in the shoes of a compiler writer.</p>
<p>You want to write a compiler that wins at benchmarks because that's one advantage. However, to do that, you need to make assumptions.</p>
<p>The UB you are talking about gives an opportunity to you as the compiler writer: your compiler can just <em>assume</em> things. This means that your compiler is fast, which is the <em>second</em> aspect on which people judge compilers.</p>
<p>But why would it not be fast if UB didn't allow that? Because if you can't <em>assume</em>, you have to <em>prove</em>.</p>
<p>An assuming compiler is faster than a proving compiler simply because proving things takes time and memory.</p>
<p>It is also simpler because the proving compiler has everything an assuming compiler does <em>plus</em> the code that proves assumptions. More code is more complex. Simple as that.</p>
<p>And that's not even the worst part in the eyes of compiler writers. The worst is that sometimes, <em>an assumption cannot be proven!</em></p>
<p>Compilers are working with Turing-complete code, and Turing-completeness means that any non-trivial property of the code may not be provable.</p>
<p>So you may implement a proving compiler, and it may fail to prove an assumption. At that point, your compiler cannot run that optimization that it really wants to run. This means that the code it generates will be slower, which means it will have a disadvantage against assuming compilers.</p>
<h2>Incorrect Code</h2>
<p>The main disadvantage is that code that <em>looks</em> correct is not. Compilers make users, programmers, fall into pits of failure, not <a href="https://blog.codinghorror.com/falling-into-the-pit-of-success/" rel="noreferrer">pits of success</a>.</p>
<h2>Conclusion</h2>
<p>So it's easy to see why compiler writers like to interpret UB the way they do: it gives them two advantages over other compiler writers that do not.</p>
<p>But is it worth it? I don't think it is, long-term.</p>
<p>You see this with C and C++: the compiler writers have taken advantage of everything they could to make the compilers as fast as possible and to generate code that runs as fast as possible.</p>
<p>And yet, people are moving away from those languages to safer languages, even to a language with a compiler that is even <em>slower</em>, Rust, because the unpredictability of those compilers makes people nervous. They want pits of success.</p>
<p>IMO, you should give that to them. Don't take advantage of the user to win some short-term gain.</p>

CC BY-SA 4.0
2023-07-16T19:43:49.723Z
2023-07-18T14:12:12.977Z
2023-07-18T14:12:12.977Z
Gavin D. Howard
CC BY-SA 4.0
2023-07-16T19:49:01.633Z
What useful assumptions woudl be enabled by the first rule that woud not be enabled just as well by the second?
supercat
CC BY-SA 4.0
2023-07-16T20:14:30.973Z
Using the pure function example, if the user labels such functions, and the compiler assumes they are correct, the compiler can eliminate multiple calls to the function with the same arguments, which can happen with macros or putting a function call in a loop condition. With a truly pure function, the function call can happen once and the result cached. If not, you pay for that call on every iteration of what could be a tight inner loop. That would be bad for performance.
Gavin D. Howard
CC BY-SA 4.0
2023-07-17T14:50:45.677Z
@GavinD.Howard: Under my proposal #2, a compiler would be allowed to perform *those same optimizations*; the *programmer* would be responsible for ensuring that any behaviors that could result from any combination of added or removed calls to the marked function would still *satisfy application requirements*.
supercat
CC BY-SA 4.0
2023-07-17T15:08:35.410Z
@supercat oh you meant #1 and #2 proposals? I thought you meant #1 being that the compiler can do anything and #2 being that the compiler cannot. Both of your proposals allow the compiler to assume things, so they are not different in any in any meaningful way; they both assume that there is an assuming compiler. So to answer your question, I believe there is not any meaningful optimization that could be done in your first proposal that couldn't be done in your second.
Gavin D. Howard
CC BY-SA 4.0
2023-07-17T15:13:40.013Z
@GavinD.Howard: Under proposal #1, a compiler given a "pure" function `int foo(int x) { if (x == 0) { printf("Skipping because it was invoked with five\n");  return 0; } else return 1000/x; }` would be allowed to compute `1000/x` unconditionally.  Under proposal #2, if `foo` would have been invoked N times when `x` was zero, a compiler could generate code that skips the call, or calls `foo(0)` more than N times, or less than N times, but would not be allowed to skip the `if` within the function.
supercat
CC BY-SA 4.0
2023-07-17T15:17:00.647Z
@GavinD.Howard: One could contrive situations where the ability to skip the `if` would be a "meaningful" optimization which would be allowed under rule #1 and forbidden under #2.  I think there are more situations, however, where optimizations allowed by #2 would be useful only if it could be done without allowing the "extra" transforms allowed by #1.  I've not seen language designs address this, however.
supercat
CC BY-SA 4.0
2023-07-17T17:13:13.587Z
While this answer is a decent overview of UB generally, I think it fails to mention an important piece of context: that UB is essentially unavoidable in a language that lacks memory safety. See [my answer](https://langdev.stackexchange.com/a/2419) for a discussion of why.
Alexis King
CC BY-SA 4.0
2023-07-17T17:28:12.113Z
@AlexisKing: There are a few situations where UB would be unavailable, but those are a tiny fraction of the situations which C, C++, and LLVM characterize as invoking "anything can happen" UB.
supercat
CC BY-SA 4.0
2023-07-17T17:35:14.987Z
@supercat I am not convinced that assertion is correct. But this comment discussion is getting a little long; feel free to join [our chat room](https://chat.stackexchange.com/rooms/146046/the-garbage-collector) if you’d like to discuss it further.
Alexis King
CC BY-SA 4.0
2023-07-18T13:58:07.677Z
`&var` can never be null in C. A better example would be `ptr->field`.
Barmar
CC BY-SA 4.0
2023-07-18T14:11:10.540Z
@Barmar: that construct takes the address of the field of the variable. If the variable is a pointer, you need that dereference to get the address of the field without special tricks. Compilers think that that dereference gives them license to assume the pointer is not NULL, and they'll change that code to just as the offset to the pointer. I've updated the answer to explain the reason better.
Gavin D. Howard
CC BY-SA 4.0
2023-07-18T14:19:44.063Z
@GavinD.Howard OOps, I was thinking `(&var)->field`, equivalent to `var.field`
Barmar
CC BY-SA 4.0
2023-07-18T15:14:08.143Z
Oh, makes sense, no worries!
Gavin D. Howard
<h2>“Anything-can-happen UB” is a <em>fundamental</em> consequence of lacking <a href="https://en.wikipedia.org/wiki/Memory_safety" rel="noreferrer">memory safety</a></h2>
<p>In order to even <em>be able to</em> restrict the set of behaviors that a buggy program may create, the compiler must be able to guarantee that at least <em>some</em> invariants will hold. One particularly important example is integrity of the call stack: if a buffer overrun overwrites a return address stored on the call stack, then a buggy program can cause the program to jump absolutely anywhere in memory when a function returns. There is simply no practical way for a language to constrain the space of possible program behaviors if this is possible, so the only implementable specification is that such errors invoke undefined behavior.</p>
<p>Technically, one way out of this conundrum would be to specify the abstract machine so precisely that you even specify things like stack layout, register allocation, mapping of the address space, and calling conventions. However, this is obviously not practical for a platform-independent programming language, and even for a language specialized to a particular architecture, it would violate even the most basic abstractions that high-level programming languages tend to provide.</p>
<p>That said, many historical C and C++ compilers <em>were</em> platform-specific, and they were essentially used as particularly fancy macro assemblers. Many of them did choose to specify extraordinarily low-level details in this way, and they were so simple that the programmer could reliably predict the code they would generate. This effectively made things like explicit stack manipulation practical and predictable, since the programmer had a mental model of the underlying machine. However, this is obviously not the way modern programming languages are used, and it would make little sense given the nature of modern optimizing compilers and instruction set architectures.</p>
<h2>Richer guarantees <em>are</em> routinely provided in memory-safe languages</h2>
<p>Most languages, even ones we call “memory safe”, include unsafe primitives that allow performing arbitrary reads and writes to memory. However, these primitives are usually only used to implement extremely low-level building blocks, which are trusted to be free of bugs. Therefore, if we ignore compiler bugs, programs written in such languages <em>do</em> respect certain invariants, even if they have bugs. This creates an operating system-like separation between the language’s “kernel” (runtime code) and its “user space” (application code).</p>
<p>Since the language can assume that runtime invariants will never be compromised, it can make stronger guarantees about how optimizations will (or won’t) preserve program structure. Your question gives the example of a compiler that permits duplicating side effects for expressions annotated as pure but otherwise behaves “sensibly”; <strong>this example is not hypothetical.</strong> GHC, the Haskell compiler, provides an operation known as <a href="https://hackage.haskell.org/package/base-4.18.0.0/docs/System-IO-Unsafe.html#v:unsafePerformIO" rel="noreferrer"><code>unsafePerformIO</code></a>, and compiler optimizations can cause duplication of side effects, but the presence of those side effects does <em>not</em> lead to “anything-can-happen UB”.</p>
<h2>Not all contract violations invoke UB even in memory-unsafe languages</h2>
<p>A guarantee similar to the one you describe could probably be made in C or C++ with respect to the C abstract machine if C or C++ had a similar purity tracking system. These specifications make a distinction between <em>undefined</em> behavior and <em>implementation-defined</em> behavior. Violations that do not have the potential to compromise the integrity of the abstract machine (without requiring undue performance overhead to prevent) are specified as implementation-defined, and the consequences of invoking implementation-defined behavior are much more limited in scope.</p>
<p>Practically speaking, there are many instances of UB in C and C++ where one could question if classifying them as UB (rather than implementation-defined behavior) is actually beneficial to anyone. However, these languages are intended to support a dizzying array of architectures, so it can sometimes be difficult to predict which invariants might be difficult to efficiently guarantee. For that reason, any operation with the potential to compromise invariants of the language runtime (which these languages do have, it is just very small) must be classified as UB to avoid placing an undue burden on compilers.</p>
<p>Generally, the best way to avoid UB is to write code in a memory-safe language.</p>

CC BY-SA 4.0
2023-07-17T17:11:45.107Z
2023-07-17T17:11:45.107Z
Alexis King
CC BY-SA 4.0
2023-07-17T17:26:30.273Z
To rephrase my main question in terms of your answer, my main focus is about the pros and cons about having "contractual" aspects of the language classify that all violations are UB, versus allowing compilers to assume that any program behaviors that could result from applying certain transforms will be viewed as *satisfying application requirements*.  Looking at my C++ code example, is there any reason the endless loop should undermine memory safety?  As processed by clang or gcc, it would.
supercat
CC BY-SA 4.0
2023-07-17T17:31:11.483Z
@supercat The intent of the second-to-last paragraph of my answer was to suggest that there is not always a very good reason C and C++ classify certain things as UB. There are even people on the standards committees who would tell you that they aren’t sure whether making such things UB was a good idea. But since C lacks the ability to say something like “this contract violation doesn’t compromise invariants of the runtime” (because the runtime has no invariants), specifying them some other way is much harder, and tossing them in the UB bucket is much easier (for better or for worse).
Alexis King
CC BY-SA 4.0
2023-07-17T17:32:55.737Z
@supercat Answering the general question of “why would they choose to make these things UB” is pretty difficult to do satisfactorily. The design space is too big, and the reasons are too varied. But I think you might be able to get an answer closer to the form you’re looking for by asking about *specific* cases of UB in the C specification. Those would permit much more detailed answers.
Alexis King
CC BY-SA 4.0
2023-07-17T18:15:57.937Z
@supercat I think the disconnect between you and Alexis comes from approaching the issue from opposite ends. The C and C++ specifications don't choose to specify certain things as undefined behavior. **Undefined behavior is the default**. To make something not UB, the specification has to describe how it works in all cases. They don't go “the behavior is defined except if X or Y”, they go “the behavior is defined if A and B”, and if your program doesn't have properties A and B, too bad.
Gilles 'SO- stop being evil'
CC BY-SA 4.0
2023-07-17T18:40:59.770Z
**Comments have been [moved to chat](https://chat.stackexchange.com/rooms/147283/discussion-on-answer-by-alexis-king-pros-and-cons-of-anything-can-happen-ub-ve); please do not continue the discussion here.** Before posting a comment below this one, please review the [purposes of comments](/help/privileges/comment). Comments that do not request clarification or suggest improvements usually belong as an [answer](/help/how-to-answer), on [meta], or in [chat]. Comments continuing discussion may be removed.
Alexis King
CC BY-SA 4.0
2023-07-19T00:00:35.383Z
To clarify a bit, "implementation-defined behavior" is a special type of UB that the C language spec requires the "implementation" (i.e., the compiler) to define and document. It's not unspecified, it's just specified somewhere else.
bta
# Pros and cons of "anything-can-happen" UB versus allowing particular deviations from sequential program execution
Posted by: username_0 on 2023-07-16
Some language specifications invite compilers to make certain assumptions, and behave in completely arbitrary fashion if such functions are violated, even if the code in question would have unambiguously defined behavior in the absence of such invitation and assumption.  This is done to uphold the principle that the behavior of any defined program execution must be consistent with all operations having been performed in the specified sequence.
It would also be possible, however, for a language specification to transform various optimizing transforms when certain conditions apply, even when they would observably affect program behavior, and for it to invite compilers to make certain assumptions for the purpose of deciding when certain transforms may be applied, but limiting their behavior in cases where the assumptions are violated to those which could result from performing the indicated transforms in a manner which is agnostic to whether the assumptions actually hold true.
Consider, for example, the following two ways of specifing the meaning of a "pure" function qualifier:

A compiler may assume that no pure function will have any side effects, and generate code that behaves in arbitrary function if this assumption is violated.

If a program has received input which--for some combination of unspecified behaviors--could result in a function being invoked with certain argument values without any additional input being received, the generated code may invoke the function any number of times, whenever the compiler sees fit (possibly skipping all invocations if a compiler can determine the return value via other means).  For purposes of determining whether a function will be invoked with particular argument values, a compiler may assume that all pure functions will return to their caller, and all loops which have a single statically-reachable exit will terminate.


If program behavior could be observably affected by a compiler's choice of when to invoke pure functions, but all behaviors that could result from any combination of choices would be equally acceptable, the second specification would make it easy for programmers to invite compilers to adjust the calling seqeunce in whatever manner would be most efficient.  Under the first behavioral model, however, programmers would be required to avoid pure qualifiers on any function that might--in any builds--need to have some side effects, even if the only side effects would be the generation of log entries indicating how the functions were called.
What advantages are there to the currently-popular approach of characterizing all executions as either fully specified or "anything can happen" UB?
ADDENDUM
To offer a more concrete example of a situation that arises using standard C syntax, consider the function:
char arr[65538];
unsigned test1(unsigned x)
{
  unsigned i=1;
  while ((unsigned short)i != x)
    i *= 3;
  if (x < 65536)
    arr[x] = 1;
  return i;
}

I can see three ways a language specification could address scenarios where x would exceed 65535, but the return value of the function would be ignored:

Specify that the loop must execute as written, preventing any subsequent code from executing.  If a compiler does this, it could consolidate the if test with the loop test, effectively performing an unconditional assignment which is sequenced after the loop exit.

Specifying that because there are no dependencies between any values that are computed in the loop and any operations that occur after it, the program may be processed as though the loop didn't exist, which would require that the if condition be evaluated.

Specifying that because the loop would fail to terminate in such cases, a compiler may generate code that behaves in arbitrary fashion, including storing 1 to arr[x] without regard for whether x is less than 65537.


If test1 is called by code that ignores the return value, both the clang and gcc C++ compilers will aggressively seek to replace the call with an unconditional assignment to arr[x], essentially causing the execution of a side-effect-free loop to trigger an arbitrary (likely memory-corrupting) side effect.
**Tags:** optimization undefined-behavior
> **Comments:**
> **username_1**: Does this answer your question? [Why would a language have a concept of undefined behavior instead of raising an error?](https://langdev.stackexchange.com/questions/481/why-would-a-language-have-a-concept-of-undefined-behavior-instead-of-raising-an)  
> **username_2**: I don't know the answer to your question, but it reminds me of this 2014 blog post from Raymond Chen that you might find interesting. https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=633  
> **username_0**: @Bbrk24: The linked suggested duplicate does not answer the question, since it addresses the question of why *some* actions must be UB, rather than the question of whether adding language rules which would classify as UB *actions which would be defined in those rules' absence" is as useful for optimization as much as other safer ways of facilitating optimization.  
> **username_3**: **Comments have been [moved to chat](https://chat.stackexchange.com/rooms/147332/discussion-on-question-by-supercat-pros-and-cons-of-anything-can-happen-ub-ver); please do not continue the discussion here.** Before posting a comment below this one, please review the [purposes of comments](/help/privileges/comment). Comments that do not request clarification or suggest improvements usually belong as an [answer](/help/how-to-answer), on [meta], or in [chat]. Comments continuing discussion may be removed.  
---
## 4 answers
### Answer by username_12 on 2023-07-16. Score: 26
As far as I know, there are only two advantages: performance and ease of writing compiler optimizations.
Performance
When compilers can make assumptions, they always make assumptions to improve performance. This is because compilers are judged on the performance of generated code.
In other words, the compiler that wins on benchmarks has an advantage.
One example of this is this construct in C:
&var->field

which takes the address of the field.
In C, dereferencing a NULL pointer is UB, and anything can happen. The compiler uses that assumption to compile that code to something like this:
value_of_var + offset_of_field

Without that bit of UB, that might need to do a check for NULL first.
Ease of Writing a Compiler
Put yourself in the shoes of a compiler writer.
You want to write a compiler that wins at benchmarks because that's one advantage. However, to do that, you need to make assumptions.
The UB you are talking about gives an opportunity to you as the compiler writer: your compiler can just assume things. This means that your compiler is fast, which is the second aspect on which people judge compilers.
But why would it not be fast if UB didn't allow that? Because if you can't assume, you have to prove.
An assuming compiler is faster than a proving compiler simply because proving things takes time and memory.
It is also simpler because the proving compiler has everything an assuming compiler does plus the code that proves assumptions. More code is more complex. Simple as that.
And that's not even the worst part in the eyes of compiler writers. The worst is that sometimes, an assumption cannot be proven!
Compilers are working with Turing-complete code, and Turing-completeness means that any non-trivial property of the code may not be provable.
So you may implement a proving compiler, and it may fail to prove an assumption. At that point, your compiler cannot run that optimization that it really wants to run. This means that the code it generates will be slower, which means it will have a disadvantage against assuming compilers.
Incorrect Code
The main disadvantage is that code that looks correct is not. Compilers make users, programmers, fall into pits of failure, not pits of success.
Conclusion
So it's easy to see why compiler writers like to interpret UB the way they do: it gives them two advantages over other compiler writers that do not.
But is it worth it? I don't think it is, long-term.
You see this with C and C++: the compiler writers have taken advantage of everything they could to make the compilers as fast as possible and to generate code that runs as fast as possible.
And yet, people are moving away from those languages to safer languages, even to a language with a compiler that is even slower, Rust, because the unpredictability of those compilers makes people nervous. They want pits of success.
IMO, you should give that to them. Don't take advantage of the user to win some short-term gain.
> **Comments:**
> **username_0** (2023-07-16): What useful assumptions woudl be enabled by the first rule that woud not be enabled just as well by the second?  
> **username_12** (2023-07-16): Using the pure function example, if the user labels such functions, and the compiler assumes they are correct, the compiler can eliminate multiple calls to the function with the same arguments, which can happen with macros or putting a function call in a loop condition. With a truly pure function, the function call can happen once and the result cached. If not, you pay for that call on every iteration of what could be a tight inner loop. That would be bad for performance.  
> **username_0** (2023-07-17): @GavinD.Howard: Under my proposal #2, a compiler would be allowed to perform *those same optimizations*; the *programmer* would be responsible for ensuring that any behaviors that could result from any combination of added or removed calls to the marked function would still *satisfy application requirements*.  
> **username_12** (2023-07-17): @supercat oh you meant #1 and #2 proposals? I thought you meant #1 being that the compiler can do anything and #2 being that the compiler cannot. Both of your proposals allow the compiler to assume things, so they are not different in any in any meaningful way; they both assume that there is an assuming compiler. So to answer your question, I believe there is not any meaningful optimization that could be done in your first proposal that couldn't be done in your second.  
> **username_0** (2023-07-17): @GavinD.Howard: Under proposal #1, a compiler given a "pure" function `int foo(int x) { if (x == 0) { printf("Skipping because it was invoked with five\n");  return 0; } else return 1000/x; }` would be allowed to compute `1000/x` unconditionally.  Under proposal #2, if `foo` would have been invoked N times when `x` was zero, a compiler could generate code that skips the call, or calls `foo(0)` more than N times, or less than N times, but would not be allowed to skip the `if` within the function.  
> **username_0** (2023-07-17): @GavinD.Howard: One could contrive situations where the ability to skip the `if` would be a "meaningful" optimization which would be allowed under rule #1 and forbidden under #2.  I think there are more situations, however, where optimizations allowed by #2 would be useful only if it could be done without allowing the "extra" transforms allowed by #1.  I've not seen language designs address this, however.  
> **username_3** (2023-07-17): While this answer is a decent overview of UB generally, I think it fails to mention an important piece of context: that UB is essentially unavoidable in a language that lacks memory safety. See [my answer](https://langdev.stackexchange.com/a/2419) for a discussion of why.  
> **username_0** (2023-07-17): @AlexisKing: There are a few situations where UB would be unavailable, but those are a tiny fraction of the situations which C, C++, and LLVM characterize as invoking "anything can happen" UB.  
> **username_3** (2023-07-17): @supercat I am not convinced that assertion is correct. But this comment discussion is getting a little long; feel free to join [our chat room](https://chat.stackexchange.com/rooms/146046/the-garbage-collector) if you’d like to discuss it further.  
> **username_13** (2023-07-18): `&var` can never be null in C. A better example would be `ptr->field`.  
> **username_12** (2023-07-18): @Barmar: that construct takes the address of the field of the variable. If the variable is a pointer, you need that dereference to get the address of the field without special tricks. Compilers think that that dereference gives them license to assume the pointer is not NULL, and they'll change that code to just as the offset to the pointer. I've updated the answer to explain the reason better.  
> **username_13** (2023-07-18): @GavinD.Howard OOps, I was thinking `(&var)->field`, equivalent to `var.field`  
> **username_12** (2023-07-18): Oh, makes sense, no worries!  
---
### Answer by username_3 on 2023-07-17. Score: 18
## “Anything-can-happen UB” is a *fundamental* consequence of lacking [memory safety](https://en.wikipedia.org/wiki/Memory_safety)

In order to even *be able to* restrict the set of behaviors that a buggy program may create, the compiler must be able to guarantee that at least *some* invariants will hold. One particularly important example is integrity of the call stack: if a buffer overrun overwrites a return address stored on the call stack, then a buggy program can cause the program to jump absolutely anywhere in memory when a function returns. There is simply no practical way for a language to constrain the space of possible program behaviors if this is possible, so the only implementable specification is that such errors invoke undefined behavior.

Technically, one way out of this conundrum would be to specify the abstract machine so precisely that you even specify things like stack layout, register allocation, mapping of the address space, and calling conventions. However, this is obviously not practical for a platform-independent programming language, and even for a language specialized to a particular architecture, it would violate even the most basic abstractions that high-level programming languages tend to provide.

That said, many historical C and C++ compilers *were* platform-specific, and they were essentially used as particularly fancy macro assemblers. Many of them did choose to specify extraordinarily low-level details in this way, and they were so simple that the programmer could reliably predict the code they would generate. This effectively made things like explicit stack manipulation practical and predictable, since the programmer had a mental model of the underlying machine. However, this is obviously not the way modern programming languages are used, and it would make little sense given the nature of modern optimizing compilers and instruction set architectures.

## Richer guarantees *are* routinely provided in memory-safe languages

Most languages, even ones we call “memory safe”, include unsafe primitives that allow performing arbitrary reads and writes to memory. However, these primitives are usually only used to implement extremely low-level building blocks, which are trusted to be free of bugs. Therefore, if we ignore compiler bugs, programs written in such languages *do* respect certain invariants, even if they have bugs. This creates an operating system-like separation between the language’s “kernel” (runtime code) and its “user space” (application code).

Since the language can assume that runtime invariants will never be compromised, it can make stronger guarantees about how optimizations will (or won’t) preserve program structure. Your question gives the example of a compiler that permits duplicating side effects for expressions annotated as pure but otherwise behaves “sensibly”; **this example is not hypothetical.** GHC, the Haskell compiler, provides an operation known as [`unsafePerformIO`](https://hackage.haskell.org/package/base-4.18.0.0/docs/System-IO-Unsafe.html#v:unsafePerformIO), and compiler optimizations can cause duplication of side effects, but the presence of those side effects does *not* lead to “anything-can-happen UB”.

## Not all contract violations invoke UB even in memory-unsafe languages

A guarantee similar to the one you describe could probably be made in C or C++ with respect to the C abstract machine if C or C++ had a similar purity tracking system. These specifications make a distinction between *undefined* behavior and *implementation-defined* behavior. Violations that do not have the potential to compromise the integrity of the abstract machine (without requiring undue performance overhead to prevent) are specified as implementation-defined, and the consequences of invoking implementation-defined behavior are much more limited in scope.

Practically speaking, there are many instances of UB in C and C++ where one could question if classifying them as UB (rather than implementation-defined behavior) is actually beneficial to anyone. However, these languages are intended to support a dizzying array of architectures, so it can sometimes be difficult to predict which invariants might be difficult to efficiently guarantee. For that reason, any operation with the potential to compromise invariants of the language runtime (which these languages do have, it is just very small) must be classified as UB to avoid placing an undue burden on compilers.

Generally, the best way to avoid UB is to write code in a memory-safe language.
> **Comments:**
> **username_0** (2023-07-17): To rephrase my main question in terms of your answer, my main focus is about the pros and cons about having "contractual" aspects of the language classify that all violations are UB, versus allowing compilers to assume that any program behaviors that could result from applying certain transforms will be viewed as *satisfying application requirements*.  Looking at my C++ code example, is there any reason the endless loop should undermine memory safety?  As processed by clang or gcc, it would.  
> **username_3** (2023-07-17): @supercat The intent of the second-to-last paragraph of my answer was to suggest that there is not always a very good reason C and C++ classify certain things as UB. There are even people on the standards committees who would tell you that they aren’t sure whether making such things UB was a good idea. But since C lacks the ability to say something like “this contract violation doesn’t compromise invariants of the runtime” (because the runtime has no invariants), specifying them some other way is much harder, and tossing them in the UB bucket is much easier (for better or for worse).  
> **username_3** (2023-07-17): @supercat Answering the general question of “why would they choose to make these things UB” is pretty difficult to do satisfactorily. The design space is too big, and the reasons are too varied. But I think you might be able to get an answer closer to the form you’re looking for by asking about *specific* cases of UB in the C specification. Those would permit much more detailed answers.  
> **username_8** (2023-07-17): @supercat I think the disconnect between you and Alexis comes from approaching the issue from opposite ends. The C and C++ specifications don't choose to specify certain things as undefined behavior. **Undefined behavior is the default**. To make something not UB, the specification has to describe how it works in all cases. They don't go “the behavior is defined except if X or Y”, they go “the behavior is defined if A and B”, and if your program doesn't have properties A and B, too bad.  
> **username_3** (2023-07-17): **Comments have been [moved to chat](https://chat.stackexchange.com/rooms/147283/discussion-on-answer-by-alexis-king-pros-and-cons-of-anything-can-happen-ub-ve); please do not continue the discussion here.** Before posting a comment below this one, please review the [purposes of comments](/help/privileges/comment). Comments that do not request clarification or suggest improvements usually belong as an [answer](/help/how-to-answer), on [meta], or in [chat]. Comments continuing discussion may be removed.  
> **username_4** (2023-07-19): To clarify a bit, "implementation-defined behavior" is a special type of UB that the C language spec requires the "implementation" (i.e., the compiler) to define and document. It's not unspecified, it's just specified somewhere else.  
---
### Answer by username_4 on 2023-07-18. Score: 3
> It would also be possible, however, for a language
> specification to transform various optimizing transforms
> when certain conditions apply, even when they would
> **observably affect program behavior**

I would find it to be very unexpected and confusing if my compiler did something that altered the way my program behaves. I expect it to do optimizations, but the overall behavior remains as written. Many languages make that an explicit requirement for the compiler. A compiler that alters program behavior means that code is much more difficult to write and debug, even if all such transformations are well-defined.

> for it to invite compilers to make certain assumptions for
> the purpose of deciding when certain transforms may be applied,
> but limiting their behavior in cases where the assumptions are
> violated to those which could result from performing the
> indicated transforms in a manner which is agnostic to whether the
> assumptions actually hold true.

You might be able to do something like this for select cases, but not in general. Much of what is labeled "undefined behavior" are things that neither the language nor the compiler can control in the first place. You can tell a compiler that it can assume all loads will be made from properly-aligned addresses. You *can't*, however, tell it what the behavior should be when that assumption is violated. That behavior varies based on the system architecture, and the compiler cannot control that. You can't reasonably specify a "manner agnostic to whether the assumptions are true" because there's no guarantee that such a thing even *exists* on all platforms.

> If program behavior could be observably affected by a
> compiler's choice of when to invoke pure functions, but all
> behaviors that could result from any combination of choices
> would be equally acceptable

You are asking the compiler to make a judgement call. Whether an outcome is "acceptable" is something only the programmer can decide. Maybe those loads are reading from memory-mapped pointers that have a side effect when they're read. Maybe the code is specifically written in a less-than-efficient way to guarantee consistent execution time to avoid timing attacks. You really don't want the compiler to start trying to decide whether something is or isn't "acceptable". Intelligent humans have a hard enough time with that.

> What advantages are there to the currently-popular approach
> of characterizing all executions as either fully specified
> or "anything can happen" UB?

Those are really the only two options. Either something is defined, or it isn't. If it's undefined, then "anything can happen" is true by definition. Note that doesn't mean that you're giving the compiler license to do anything that it wants to do. It simply warns the programmer that the spec alone can't predict what will happen when that code is executed so it's best to assume the worst. As a simple example, the code emitted for `x << y` is undefined when `y` is negative. The compiler still has to generate code that works for valid values of `y`, but there's no telling what that same code will do when `y` is negative. C doesn't have a way of reporting error conditions for operators, so they get called "undefined behavior" to let the programmer know that it's *their* responsibility to check these things.

That being said, I think you're forgetting the way that this normally plays out in practice. Many things classified as UB set boundaries for the language and its usage. When programmers wander off into UB territory, it's usually either unintentional (a bug) or they're doing something they shouldn't be doing in the first place (writing awful code). I scanned through the list of Undefined Behavior in section A.6.2 of the C89 spec, and the types of things listed there are cases where things have really run off the rails. When a compiler sees those in a program it doesn't result in "anything goes" output code, it gets flagged as an error. Undefined Behavior means the compiler doesn't know what it should do. In practically all of these cases, the correct response is to bail out and gripe at the user. Compilers are supposed to be picky.

Regarding your sample code:

> 1. Specify that the loop must execute as written, preventing
>    any subsequent code from executing. If a compiler does this, it
>    could consolidate the `if` test with the loop test, effectively
>    performing an unconditional assignment which is sequenced after
>    the loop exit.

It seems unlikely that a compiler would do this since the only way to know that this is possible is to determine that the loop never terminates. If that's the case, then the code following it doesn't matter. Therefore, there's no advantage in doing the extra work to optimize the effectively-dead code. Just keep things simple and either emit the code as-is with no optimizations, or delete it all. Selectively deleting only *part* of your dead code just means more work for the compiler writer for no benefit.

> 2. Specifying that because there are no dependencies between
>    any values that are computed in the loop and any operations that
>    occur after it, the program may be processed as though the loop
>    didn't exist, which would require that the if condition be evaluated.

`gcc` seems to do this, at least according to the documentation (it's mentioned in section 14.8). Optimizations are outside the spec of the language, but the compiler should document this behavior.

> 3. Specifying that because the loop would fail to terminate in
>    such cases, a compiler may generate code that behaves in
>    arbitrary fashion, including storing 1 to arr[x] without regard
>    for whether x is less than 65537.

There's a huge difference between "we can't predict exactly what a piece of code will do" (as in what happens when you overrun an array) and "I'm just going to wing it and generate whatever code I feel like generating". The compiler should generate code that matches the functionality of what the programmer wrote. Sometimes that code may not behave predictably. That's not the compiler's fault. Eliminating the `if` would only make sense in cases where the function was inlined and it was called with a constant argument that was less than 65536. For larger input values or where the `if` can't be evaluated at compile time, an unconditional assignment would be incorrect.

> If `test1` is called by code that ignores the return value, both
> the clang and gcc C++ compilers will aggressively seek to replace
> the call with an unconditional assignment to `arr[x]`, essentially
> causing the execution of a side-effect-free loop to trigger an
> arbitrary (likely memory-corrupting) side effect.

In this particular case, it doesn't feel like the result you describe is intentional. Removing an array bounds check shouldn't be done unless you can be absolutely certain that it'll never fail. It should be obvious to the compiler that the `if` in this code is a bounds check for the following line. This really feels like you have the optimizations cranked way up, and there's a problem with the way several of the optimizers and the inliner are interacting (eliminating the loop and `i` is understandable, but `x` and the bounds check is not). I recommend asking about this on the gcc or clang mailing lists.
> **Comments:**
> **username_0** (2023-07-19): The rules of c++ allow loops to be optimized according to #3, *and that's precisely what clang and gcc actually do*.  Since the code after the loop would be unreachable if `x` were greater than 65535, they eliminate the `if`, and if the value of `i` is never observed they'll eliminate the loop as well.  I think optimizers embracing such nonsense should be recognized as unsuitable for any programs that might receive potentially malicious input, the maintainers of clang and gcc claim people want compilers to perform such "optimizations".  
> **username_0** (2023-07-19): //You can't reasonably specify a "manner agnostic to whether the assumptions are true" because there's no guarantee that such a thing even exists on all platforms.// Many programs are not written for abstract machines, but for real computers, and in many cases programmers may know things about the target computers that the implementation can't.  If the programmer knows that a program will only run on platforms where a read of an arbitrary address will never have any effect beyond yielding a possibly-meaningless value, machine code that exploits this may be more efficient than...  
> **username_0** (2023-07-19): ...machine code that avoids out-of-bounds reads.  A programmer targeting a compiler that processes reads in a fashion which whose effects will always be limited to performing an actual read with whatever consequences result and/or yielding a possibly meaningless value, and a platform which is known to process reads in side-effect-free fashion, could allow such a compiler to generate more efficient code than would be possible if the source code were written to prevent out-of-bounds reads at all costs.  
> **username_0** (2023-07-19): //When programmers wander off into UB territory, it's usually either unintentional (a bug) or they're doing something they shouldn't be doing in the first place (writing awful code).// Why do you suppose the Standard describes UB as resulting from "non-portable or erroneous program constructs...", rather than "program constructs which are erroneous (rendering portability irrelevant)"?  C's reputation for speed came about because it let programmers exploit constructs which their intended target platforms were known to process usefully.  Code exploiting such constructs would only be portable...  
> **username_0** (2023-07-19): ...among platforms that processed those constructs in expected fashion, but there was no intention to require that programmers subject themselves to limitations that would be imposed by *platforms upon which code would never be executed*.  
> **username_0** (2023-07-19): //You are asking the compiler to make a judgement call. Whether an outcome is "acceptable" is something only the programmer can decide.// Indeed, and a programmer that would regard variety of behaviors as acceptable, but some other behaviors as unacceptable, would *tell* the compiler that by writing the code in a manner that would allow a compiler to choose freely among those the programmer viewed as acceptable, but not include among the allowable behaviors any that the programmer would regard as unacceptable.  
> **username_5** (2023-08-03): @bta There is a 3rd option between Fully Specified and "Anything can happen" UB: *Implementation Defined*. You won't find the exact details of the behavior in the language spec, instead you'll have to look at your implementation's documentation. Obviously, this loses portability, but it does offer consistent, reliable behavior. Implementation A might say a left shift with a negative number turns into a right shift by the absolute value and implementation B might say it's a no-op, but neither one will spontaneously summon nasal demons if they don't say they will (bugs aside).  
> **username_0** (2023-11-10): @8bittree: A fundamental difference between "Implementation Defined" behavior and UB is that any action whose behavior might conceivably be affected by optimizing transforms in any observable way must be classified as UB, even if the transform would replace one behavior meeting application requirements with a different behavior that would--despite being different--still satisfy many application's requirements.  
> **username_0** (2024-07-10): @8bittree: I should have further clarified that between saying that "`int1=x*30/15`" will process the multiply using precise two's-complement semantics" and `int1=x*30/15` may behave in arbitrary fashion if `x` is larger than `INT_MAX/30`, would be a third possibility of saying that when `x` exceeds `INT_MAX/30`, `int1` may be assigned any number *without side effects*.  I've come to realize that present compiler designs are ill equipped to handle the notion that a compiler could *either* replace the code with `int1=x*2` *or* use truncating two's-complement multiply and exploit...  
> **username_0** (2024-07-10): ...the fact that `int1` will always be in the range `INT_MIN/15` to `INT_MAX/30`, but that a compiler could only exploit the latter post condition if it *actually generated code* that would guarantee it.  Much easier to write language rules in such a way that forbids programmers from writing code that would allow compilers a choice of optimizations that would be fine individually, but must be treated as mutaully exclusive.  
---
### Answer by username_6 on 2023-07-17. Score: -2
If there are some *know or expected* particular deviations, then these deviations are *defined* somewhat. But the thing with UB is that it really means undefined, anything goes. See some example below.

Focusing entirely on the title and the last paragraph:

> What advantages are there to the currently-popular approach of characterizing all executions as either fully specified or "anything can happen" UB?

[UB is all or nothing.](https://predr.ag/blog/falsehoods-programmers-believe-about-undefined-behavior/#:%7E:text=Falsehoods%20about%20the%20possible%20outcomes%20of%20UB) If *one* very small and very local thing is undefined behavior anything can happen, then *all things can happen*, and nothing can be expected afterwards.

UB is contagious. UB cannot be contained. After it happens, [all your disks can be zero](https://blog.tchatzigiannakis.com/undefined-behavior-can-literally-erase-your-hard-disk/). Code guarding against UB can and [probably will be deleted](http://blog.pkh.me/p/37-gcc-undefined-behaviors-are-getting-wild.html). There is no profit in speculating otherwise.

Also, [nasal demons](http://www.catb.org/jargon/html/N/nasal-demons.html).

---

As for the pros/cons of letting UB happen, the principal reason is performance, and this can be [measured](https://cs.stanford.edu/%7Ezhihao/papers/apsys12.pdf).
> **Comments:**
> **username_0** (2023-07-17): In what way would the specification #2 in the question not be superior to the former approach for declaring "pure" functions.  Present compilers may not be capable of usefully exploiting such semantics, but that's no reason why future languages shouldn't be designed better.  
> **username_7** (2023-07-17): I don't think this answer adds any valuable information. We already know that UB means anything can happen, the question is what the pros/cons are for *letting* this happen.  
> **username_0** (2023-07-17): @G.Sliepen: My point is that a well-written spec shouldn't say "anything can happen" in all situations where a useful optimizing transform might result in a program behaving in a manner which is inconsistent with sequential program execution *but may still meet application requirements*.  If a programmer writes `do { x=foo(1); y=bar(z); ...}` and both function calls generate logs but are *otherwise* side-effect free, having N executions of the loop result in one log entry for `bar` followed by N entries for `foo` would be inconsistent with sequential program execution, but ...  
> **username_0** (2023-07-17): ...any combination of log entries for calls to `foo` and `bar` would satisfy application requirements equally well, granting a compiler the freedom to choose among many behaviors *that all satisfy application requirements* would be more useful than saying that the only way to guarantee anything is to explicitly force everything.  
> **username_8** (2023-07-17): @G.Sliepen You can't avoid “letting” this happen unless you can ensure that nothing slips through the cracks. In a language without memory safety, it's impossible to guarantee that nothing slips through the cracks.  
> **username_9** (2023-07-17): @supercat such a spec would make assumptions about the underlying platform that c doesn't provide. For example, there exist c platforms (without any optimisation) where a ub pointer dereference can result in a motor spinning up.  
> **username_0** (2023-07-17): @user1937198: On an Apple II with a floppy controller in slot #6 (the usual place), reading address $C0E9 will turn on the floppy drive motor, and reading address $C0EF while the motor is running will trash whatever track is under the drive head. From a language perspective, however, one could specify a "low level C" dialect where read-dereferencing a `char volatile*` holding address $C0E9 would synchronize the abstract and physical machine states and then instruct the environment to perform a load from address $C0E9.  The language spec wouldn't care about what the environment...  
> **username_0** (2023-07-17): ...would do when instructed to perform the load, and a programmer who didn't know how the environment would process the load would have no way of knowing what the consequences might be, but a programmer who knew what the environment would do in response to such a load instruction would be entitled to rely on that behavior.  An access not qualified `volatile` would be somewhat similar, except that accesses might sometimes be "cached", hoisted, deferred, and/or consolidated.  
> **username_9** (2023-07-17): @supercat now I have a pointer to a char array at $1000, and attempt to write out of bounds to index $B0EF, how do you stop that trashing the disk without runtime bounds checking?  
> **username_9** (2023-07-17): Without memory safety,  you can't prevent arbitrary memory load/stores, and without constraints on memory mapped io, you can't stop arbitrary load/stores having arbitrary effects. And thus any language that does not have memory safety,  and does not place constraints on memory mapped io, must have nasal demon ub.  
> **username_9** (2023-07-17): And there are other possibilities of escape.   Without WxE protection, an invalid memory write could modify code, which when executed would have behaviour dependent on the machine code of the system, and could do anything that is possible in machine code.  
> **username_9** (2023-07-17): You can list the explicit transforms under which a write can be moved, but if a write can result in arbitrary behaviour when out of bounds, and that behaviour can be hoisted, then you are effectively allowing time traveling undefined behaviour. It is entirely possible for a hoisted out of bounds write to attempt to write to memory that would corrupt control flow between the ordinal write point and thehoist point. At which point anything can happen.  
> **username_0** (2023-07-17): @user1937198: In typical low-level dialects of C, the array access would be processed as a load or store from/to address `$C0EF`; if the execution environment dopcuments the effect of such access, a low-level implementation would process it "in a documented manner characteristic of the environment".  The Standard shouldn't concern itself with what the environment's documented characteristic action would be, or whether it would be desirable or undesirable.  
> **username_9** (2023-07-17): @supercat and to do so is a) implementation defined, and b) either limits your ability to move around accesses, as effectively all accesses must be treated as volatile unless proven otherwise, as all accesses have the potential to break memory safety,  or you effectively dump the documentation of the optimizer as your implementation defined behaviour spec, and end up with a spec so complex no one understands it  
> **username_9** (2023-07-17): The issue with undefined behaviour vs implementation defined behaviour is that undefined behaviour usually comes from at least a platform where there is a potential subset of that behaviour, where if executed, even with no transforms, the compiler can no longer guarantee the runtime state of the program at all.  
> **username_9** (2023-07-17): You are escaping the abstract model, and a sufficiently detailed implementation spec would be the ISA spec and associated translation details, along with any optimisations. Consider the case of an out of bounds write of program code, the only way to define it is to include how the write translates to machine code, and what machine code is already present.  
> **username_0** (2023-07-17): @user1937198: How does the Standard seek to characterize actions which most implementations would process in the same useful fashion, but where on a few implementations it would be expensive to guarantee any behavior consistent with sequential program execution, and no possible behavioral spec would be useful?  Classifying an action as "Implementation-Defined" means that all implementations must document an action consistent with the overall abstraction model.  
> **username_0** (2023-07-17): @user1937198: //either limits your ability to move around accesses, as effectively all accesses must be treated as volatile unless proven otherwise// I specifically allowed for the possibility that compilers may reorder memory accesses, subject to various rules.  Making decisions about whether memory accesses may be reordered based upon static aspects of program structure is vastly easier than trying to *reliably* make determinations based upon dynamic aspects of program execution.  If one says that programmers requiring that actions occur in a particular sequence must ensure...  
> **username_0** (2023-07-17): ...that there is at least one rule that would force the accesses to occur in that sequence would seem much saner than having rules which define actions whose behavior is obviously meant to be defined as equivalent to actions whose behavior is expressly characterized as undefined.  
> **username_9** (2023-07-17): Due to the politics of the C/C++ standards, its usually an all or nothing of undefined vs implementation defined behaviour.  
> **username_9** (2023-07-17): allowing reordering subject to rules is the problem. what if the write modifies the code under execution to such that on a specific microarchitecture it results in a jump to non-code between the original and reordered write. You now have an apparently arbitrary subset of the code not being executed, and something completely different being executed instead. And so rather than trying to cover all these cases, the standard just goes, if you break memory safety,  all bets are off.  
> **username_10** (2023-07-17): **Comments have been [moved to chat](https://chat.stackexchange.com/rooms/147290/discussion-on-answer-by-andre-l-f-s-bacci-pros-and-cons-of-anything-can-happen); please do not continue the discussion here.** Before posting a comment below this one, please review the [purposes of comments](/help/privileges/comment). Comments that do not request clarification or suggest improvements usually belong as an [answer](/help/how-to-answer), on [meta], or in [chat]. Comments continuing discussion may be removed.  
> **username_11** (2023-07-19): Counterexample: signed integer overflow in C.  The original purpose of making it UB was to let compilers convert it to the native machine instructions regardless of how the machine represented signed integers.  This produced a finite and rather small set of "undefined" behaviors.  But since it's "anything can happen"-UB rather than bounded UB, programmers needs to wrap any signed integer operations that might overflow with elaborate guard constructs to prevent the compiler from sabotaging them.  
> **username_0** (2023-07-20): @Mark: In most kinds of code, having a compiler transform a construct like `x*30/15` into `x*2` would offer significant performance benefits without breaking programs that could have been better written to indicate programmer intent, but because the effects of such a transform would be observable, the as-if rule would prevent the optimization unless the action is classified as "anything can happen" UB.  
> **username_11** (2023-07-20): @supercat, you can still perform that transform under "bounded" UB.  If the result of any signed-integer operation is required to still be a signed integer, leaving it unspecified as to *which* signed integer results from overflow, most useful optimizations are still possible.  
> **username_0** (2023-07-21): @Mark: One could specify things in such fashion, but the phrase "Implementation-Defined behavior" is reserved for things that implementations were expected to document in greater detail than that.  
---
