The compiler doesn't need to follow the order of operations as long as the observed behavior is the same as if the operations were performed in the order in the code. Since the compiler doesn't have any responsibility if division by zero happens, it's fine to do the division before.
This is an interesting read: https://blogs.msdn.microsoft.com/oldnewthing/20140627-00/?p=...
It can reason: because it's undefined, it won't happen, that is, the code will never divide by zero. Therefore, the divisor is non-zero, and so the division instruction will not trap. Which means it can be moved above the call.
If you mentally replace "undefined" with "impossible", the compiler behavior becomes more clear. And yeah, even in the absence of undefined behavior, optimizing compilers reason about impossible behavior all the time; for instance, after an "if (x == 2)", the compiler knows it's impossible for x to be 2 at the start of the else branch.
Undefined behavior can ‘go back in time’, but only to a point where the compiler can prove it will necessarily occur in the future. And there’s no language in the standard allowing compilers to assume that an arbitrary function will ever return, as opposed to terminating the program or entering an infinite loop.
There are some cases where the compiler is allowed to assume a loop will eventually terminate (one of the less widely known forms of undefined behavior), but only if the loop has no input/output or other observable effects besides spinning:
https://stackoverflow.com/questions/16436237/is-while1-undef...
Edit: And according to the linked Debian bug report thread, the problematic optimization could no longer be reproduced on newer versions of GCC.
I don't quite get what you mean here. If the compiler can prove that you perform undefined behavior, isn't it legal for it to move it to the start of the program and literally produce no output?
Yes, it can! (Or worse, like making it erase all your files.) But that's only if it can prove that the program will always reach the undefined behavior. The path a program takes can depend on its input, command line parameters, contents of the filesystem, or even the phase of the moon (literally: the program can ask the operating system for the current date and time, and branch on a value derived from them). If a program will only reach undefined behavior once in a blue moon, the compiler is not allowed to output an executable which will fail when ran on dates which are not considered a blue moon.
Edit: that is, with any input that causes it to be altered. The most famous cases of UB involve compilers removing code entirely or optimizing an expression to a constant, but it’s just as valid (and more useful) to replace one computation with another computation (e.g. an unsigned division with a signed division) that produces the same result for “legal” inputs, but potentially a different result for “illegal” inputs – where “illegal” means “given that input at that point in the program, the program necessarily has executed or will execute UB”. From the standard’s perspective, the question is not which instructions are generated or what computations are actually performed, but whether the observable behavior is the same “as if” the computations in the source code were performed, for all execution traces not containing UB. (Just to reword myself in increasingly verbose fashion.)
Actually, it can because one of the things undefined behavior can do is go backwards in time.
Undefined behavior anywhere in the program can legally make any part of the program do anything at any time.
On top of that, there is no reliable way to tell if your program contains undefined behavior.
This is madness.
This is only true if the undefined behavior is guaranteed to be reachable.
This is not true in general.
Putting "if (0) 1/0;" in a program does not cause undefined behavior.
The C standard writers may have been unwise to specify undefined behaviour the way they did, but they weren't THAT crazy!
Nope. It's true if the UB is possibly reachable.
See: https://blogs.msdn.microsoft.com/oldnewthing/20140627-00/?p=...
In other words, it can travel backward, but not sideways (which is what you originally claimed).
Of course they can. UB licenses the program to do anything including re-write any part of itself.
Do you think this program can "do anything" or "re-write any part of itself"?
unsigned int x = <input from user>;
unsigned int y = <input from user>;
printf("%u\n", x / y);
The compiler must output valid code for that program, and execution (not compilation) can only "do anything" if the user enters values that invoke UB (like "y == 0" for example). For all valid inputs this program must execute correctly and can not "do anything", even at runtime, do you agree?What about this one?
unsigned int x = <input from user>;
unsigned int y = <input from user>;
if (y == 0) {
printf("%u\n", x / y);
} else {
printf("%u\n", x * y);
}
Same idea: this program can only "do anything" if the user enters "y == 0". The compiler can see that "x / y" would invoke UB, and remove the entire "if (y == 0) { ... }" branch (because the compiler can assume it is never reached, otherwise it would invoke UB), but the compiler can not do anything to change the behavior of the "else" branch - entering any non-zero "y" value must produce correct output, so the program can't just be compiled to "do anything".Only the paths related to the UB can be optimized/modified/rewritten by the compiler based on that UB, and if those optimizations or modifications affect the proper execution of paths unrelated to the UB (i.e. paths where the UB can not be reached from), then the optimization or modification in question is a compiler bug.
That's true, but it doesn't help much.
> Do you think this program can "do anything" or "re-write any part of itself"?
Yes. Not for all inputs, but yes.
> this program can only "do anything" if the user enters "y == 0".
Nope. There is at least one other input value that produces UB.
And this is exactly the problem: even on a trivial example that you contrived, you cannot fully enumerate all the circumstances that produce UB. For real programs, the situation is hopeless.
Which proves my point: a program can't "do anything" just because some part of it may contain UB. Only the UB paths are allowed to "do anything".
> There is at least one other input value that produces UB.
Not in my example there isn't (assuming proper context for the example code, i.e. you aren't doing "#define printf (something evil)", you include the right headers, you define main properly, etc, etc).
> And this is exactly the problem: even on a trivial example that you contrived, you cannot fully enumerate all the circumstances that produce UB
The problem is that you don't really understand UB fully and are making incorrect claims about it repeatedly.
I'm not claiming UB can't cause problems, but it doesn't cause problems in some of the ways you've been quite vocal about.
Want to bet? How much are you willing to wager? (And yes, I accept all the conditions you listed.)
> The problem is that you don't really understand UB fully
Then this is a chance for you to make some easy money.
I never said they could.
> I do like to know when I make mistakes
I doubt that very much, but let's do the experiment:
Here is one of your many mistakes:
"Even in the time travel examples, the code paths unrelated to the ub can't be touched."
That's simply not true. UB licenses a program to do anything, including overwriting itself. Which means that the compiler can anticipate this and emit arbitrary code.
Now, you might, if you are very very careful, be able to construct a program that contains UB that is guaranteed to behave as intended for some non-zero time interval. You might even be able to construct a program that contains no UB at all, though this is even more challenging. Writing a non-trivial C program that contain no UB is practically impossible. I doubt there is even a single such program in existence in the world today. Your trivial examples both contain UB. And no, I'm not going to tell you what it is because that would be missing the point. The point is not that I know some detail of the C standard that you have overlooked (though that is in fact true). The point is that you think you can write correct C code, and you can't. No one can.
> I'll fully own up to it if I did.
I doubt that very much too, but I'm ready to be surprised.
Only if it is reachable under the given inputs. We've gone over this, and you were on the right track a few comments up. Why revert now?
> Writing a non-trivial C program that contain no UB is practically impossible > The point is that you think you can write correct C code
We happen to agree on both of these points, but I'm not sure how you got on this topic? Let's stay focused...
> And no, I'm not going to tell you what it is because that would be missing the point.
That's about what I expected, since there's no UB in my example.
Good day!
Yes, I already conceded that.
> there's no UB in my example
Then here is an opportunity for you to show me up and make some easy money. How much are you willing to bet?
The ball's in your court man. Posturing around a wager is just you flailing around, empty handed.
https://spin.atomicobject.com/2014/05/19/c-undefined-behavio...
but I just noticed that you actually specified your ints to be unsigned. So I would have lost. (And I would have paid.)
I almost missed this one! Here's some choice quotes for you:
From https://news.ycombinator.com/item?id=16917162
> "Undefined behavior anywhere in the program can legally make any part of the program do anything at any time."
and from https://news.ycombinator.com/item?id=16917551
> "It's true if the UB is possibly reachable."
https://github.com/kframework/c-semantics/blob/master/README...