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?
To offer a more concrete example of a situation that arises using standard C syntax, consider the function:
unsigned test1(unsigned x)
while ((unsigned short)i != x)
i *= 3;
if (x < 65536)
arr[x] = 1;
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
iftest 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
ifcondition 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
xis less than 65537.
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.