This one came as a surprise to me. Is this really (unconditionally) true? And if so, why? Seems trivial to me to ensure that UB that will never under any circumstance execute will not affect the functioning of an otherwise correct program.
This one came as a surprise to me. Is this really (unconditionally) true? And if so, why? Seems trivial to me to ensure that UB that will never under any circumstance execute will not affect the functioning of an otherwise correct program.
The compiler can't know that an arbitrary part of a function is unreachable if, for instance, that code path is controlled by a parameter or global state since that equates to solving the halting problem.
https://blog.llvm.org/2011/05/what-every-c-programmer-should...
DR #109 specifically states that a compiler may not fail to translate a strictly conforming program just because some possible executions could trigger UB: https://www.open-std.org/jtc1/sc22/wg14/docs/rr/dr_109.html
Edit: downvoters, please explain your disagreement. I cited a primary document in support of my position.
It's just that the generated code may not do exactly what you expect it to do because the presence of undefined behavior allowed the compiler to make assumptions which may surprise you.
edit: It seems that I am incorrect
> A conforming implementation must not fail to translate a strictly conforming program simply because some possible execution of that program would result in undefined behavior.
This text specifically allows for the case that a program is strictly conforming even if there is a possible execution that invokes UB. If a program is strictly conforming, it must produce the correct behavior.
I would challenge you to show an example where GCC or Clang will break the correctness of a program on account of UB that is not reached during program execution.
Here is an example where GCC and Clang specifically respects the correctness of a program as long as it does not reach the UB: https://godbolt.org/z/befWah77W
At the very least I am unable to construct a counter-example so I concede this point
> The Rust compiler has a few assumptions that it makes about the behavior of all code. Violations of those assumptions are referred to as Undefined Behavior.
Then it says
> In other words, even just constructing, for example, an invalid bool, is Undefined Behavior—no matter whether that bool is ever actually “used” by the program.
The blog post is talking about how just creating an invalid/trap/niche representation for a value is UB. The act of creating the value is the UB here, not the usage. So, in this case the UB is most definetely reachable and executing (The act of creating the invalid value).
Or, here is a better example:
for(int i=0; i > n;) {
}
*p=1;
Here the compiler will assume that the loop terminates, since non-terminating loops without side-effects are UB in C++.Then, since the loop terminates, p will be dereferenced, so it can be assumed that p is never NULL, so any NULL checks for p in this context can be ellided.
This remains valid even if you call this function with an n such that *p=1 is unreachable.
> Furthermore, if every possible execution of a given program would result in undefined behavior, the given program is not strictly conforming. A conforming implementation must not fail to translate a strictly conforming program simply because some possible execution of that program would result in undefined behavior. Because foo might never be called, the example given must be successfully translated by a conforming implementation.
To hammer it in, the consider the following:
if(p) *p = 0;
*p is undefined if p is NULL. But as long as that statement is never reached when p is NULL that is not a problem. That is trivially true in this case, but the same applies even for more complex relations between the conditional and the undefinedness of the statement.
Or put another way, in
int * p = 0;
if(fermats_last_theorem_is_wrong()) {
p = malloc(sizeof(int));
*p = 0;
}
if(p) {
puts("fermats last theorem is wrong\n");
}
if(fermats_last_theorem_is_wrong()) {
*p == 1;
}
if(p) {
puts("fermats last theorem is wrong\n");
}
the compiler is not allowed to remove the if(p) checks and unconditionally print that fermats last theorem is wrong unless it can prove that fermats_last_theorem_is_wrong() is true.In fact, something having undefined behavior and the compiler being allowed to assume that it is unreachable are effectively the same thing. Which is why the spec for std::unreachable() and the compiler-specific intrinsics that preceded it is that it invokes undefined behavior and not more complex wording.
No it won't. Otherwise all precondition checks would be meaningless. The compiler can only assume that it isn't null when fermats_last_theorem_is_wrong() is true. If the compiler cannot reason about fermats_last_theorem_is_wrong() then it can't just ignore it.
> for(int i=0; i > n;) { } *p=1;
> Here the compiler will assume that the loop terminates, since non-terminating loops without side-effects are UB in C++.
Infinite loops are UB precisely because otherwise the compiler would NOT be able to reason about this. So yes, here the comiler can assume that p is a valid pointer - but only because it can also assume that *p=1 is NOT dead. This falls entirely under the umbrella of behavior not being defined at all after you execute undefined behavior (the infinite loop).
> This remains valid even if you call this function with an n such that *p=1 is unreachable.
Only because the loop itself is undefined behavior in that case.
The example with the loop though is more to point out that our native understanding of "unreachable" may be different than the compiler's. The same problem could occur without invoking UB if we un-conditionally generate a SIGKILL to our own process and then dereference a NULL pointer - the compiler doesn't know what SIGKILL does, so it doesn't know the UB is unreachable in practice, so it may reorder etc.
It is the reverse. If the compiler doesn't know what raise(SIGKILL) (or really, any other function it cannot see through) does, it has to assume that it can potentially never terminate, abort or otherwise change the control flow, so it can't safely reorder across it. And even if it does return, it could still inspect memory and observer the violation of the as-if rule.
That's why for example pthread_mutex_lock/unlock worked correctly for the most part even before C++ got a proper concurrent memory model.
An infinite loop is not UB. It is well defined. Just the definition of a terminating loop is a bit funny.
> An iteration statement whose controlling expression is not a constant expression, that performs no input/output operations, does not access volatile objects, and performs no synchronization or atomic operations in its body, controlling expression, or (in the case of a for statement) its expression, may be assumed by the implementation to terminate.
Plus:
> An omitted controlling expression is replaced by a nonzero constant, which is a constant expression.
The compiler may only use as-if rule here if it can deduce the value is constant or limited. It can (but does not have to) assume the loop will end due to the conditional and lack of the other features. Which is still defined behavior. (since C11 at least, probably earlier)
From C11:
> An iteration statement whose controlling expression is not a constant expression,156) that performs no input/output operations, does not access volatile objects, and performs no synchronization or atomic operations in its body, controlling expression, or (in the case of a for statement) its expression-3, may be assumed by the implementation to terminate.157)
> 157)This is intended to allow compiler transformations such as removal of empty loops even when termination cannot be proven.
This makes infinite loops UB unless they fall under one of these exceptions (constant controlling expression, performs I/O, accesses volatile objects or atomics).
It only allows the compiler to assume loops with non-constant expressions can terminate. (And a bunch of other exceptions where it cannot.) A true infinite loop has a constant expression as the conditional and cannot be optimized away based on this rule.
What this rule allows the compiler to do is to optimize away loops where number of iterations are known ahead of time if it has a non-constant expression condition.
That is basically what I said: loops can be assumed to terminate, except for an enumerated set of exceptions where infinite loops are allowed.
You seem to be defining "true infinite loop" to mean "while(1)" or "for(;;)" exclusively. But "for (int i = 0; i < 1;)" is also an infinite loop, and that one is UB since the controlling expression is not constant.
> What this rule allows the compiler to do is to optimize away loops where number of iterations are known ahead of time if it has a non-constant expression condition.
If the number of iterations are known ahead of time, then it is not an infinite loop so I don't see how the rule would apply.
The rationale for making infinite loops UB is described here: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1528.htm
Specifically, it allows for transformations like turning:
for (p = q; p != 0; p = p -> next) {
++count;
}
for (p = q; p != 0; p = p -> next) {
++count2;
}
into: for (p = q; p != 0; p = p -> next) {
++count;
++count2;
}for (;;) { }; cannot be assumed to have finite number of executions. Therefore, it cannot be optimized away to finite execution.
You're showing potentially infinite loops with non-constant expression as condition.
for (; i != 0; ) {} can be assumed to have finite number of executions and can be optimized unless i is constant expression.
Yes, because a constant controlling expression is one of the enumerated exceptions to the rule against infinite loops.
> You're showing potentially infinite loops with non-constant expression as condition.
"for (int i = 0; i < 1;)" is an infinite loop, full stop. It is not potentially infinite, it is infinite under all possible executions. It is UB because it does not fall under one of the enumerated exceptions where an infinite loop is allowed.
I think we are saying the same thing, the only difference is that you seem to be defining "infinite loop" to only include loops with constant controlling expressions, whereas I am defining it to mean "any loop that does not terminate."
Please correct my misunderstanding...
if (p is not NULL) {
dereference p
}
is UB? That's crazy, but not actually unbelievable.The compiler has to respect DR 106. The linker, however, is allowed to write garbage into code.