gcc: “hold my beer while I optimize out that block of code you clearly didn’t mean to be in your program”
gcc: “hold my beer while I optimize out that block of code you clearly didn’t mean to be in your program”
The galling thing is that they did this to win a benchmark war against Fortran.
UB was introduced to allow skipping checks that otherwise might have added overhead, like array bound checks or null checks or to give more flexibility to how correct programs are optimized (e.g. freedom to evaluate arguments in any order). Therefore each compiler/library/os vendor could implement these cases differently.
However, it was never meant to allow compiler to assume "ok, I can see your code is totally broken, so let's break it more, cause you don't care anyway".
3.4.3 undefined behavior
behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements
NOTE Possible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).
-------------
Even if you want to argue over what "imposes no requirements" means, I'd argue that "ignoring the situation completely with unpredictable results" is very clear.
The note also says (and elsewhere in the standard it reinforces this) that since there are no requirements, implementations could absolutely do things like perform runtime checks, etc. Compilers do not do this though. This isn't for no reason, but that's a separate issue.
That's nevertheless how compiler writers are interpreting the standard right now. Take integer overflow for instance. Stuff like this:
unsigned u = some_computation();
int x = u; // because of reasons
x += 42; // hmm this may overflow, let's check that
if (x < 0) { // It's a 2's complement machine, this'll work
handle_overflow(x); // now, let's deal with the overflow
}
Now here's how your optimising compiler see that stuff: unsigned u = some_computation();
int x = u; // x is always positive.
x += 42; // signed integer never overflow
if (x < 0) { // x was positive, no overflow... always false
handle_overflow(x); // Let's delete this dead code.
}
Then, what actually happens: unsigned u = some_computation();
int x = u;
x += 42;
This is insane.[1] https://wandbox.org/permlink/dDDAJjOlm50GD1PW
It would be able to (correctly) make that assumption if `x` were a long, assuming 64-bit longs and 32-bit int/unsigneds.
When a value with integer type is converted to another integer type ... [if] the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised.
http://www.open-std.org/jtc1/sc22/WG14/www/docs/n1570.pdf
So casts from unsigned to signed must have a well-defined result, but the implementation can choose how to define it. You could in theory have an implementation that defines that casts from unsigned to signed are always positive,[1] but since that's not how GCC and every other mainstream (existing?) compiler defines it, they are not allowed to assume the result of a cast from an arbitrary unsigned integer to a signed integer of equal width is positive.
[1] In which case the compiler would be allowed to assume that unsigned-to-signed casts are never negative. This assumption would however be both safe and correct, and would have nothing to do with UB.
Let's see, that's not an assumption you can make in C, or is it? I seem to remember that a compiler is allowed to present to the programmer at least one or two representations other than two's complement.
And a footnote on that:
"A positional representation of integers that uses the binary digits 0 and 1, in which the values represented by successive bits are additive, begin with 1, and are multiplied by successive integral powers of 2, except perhaps the bit with the highest position."
Or rather, because the standard "imposes no requirements". A compiler that does the obvious/sensible thing for UB can also claim conformance.
From C99 3.4.3 paragraph 3 (emphasis mine): "Possible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message)."