Actually I didn't. My point was rather obvious: the whole point of the standards specifying UB is precisely to let implementations define the behavior themselves.
That's literally the definition of implementation-defined behavior.
Undefined behavior really means undefined; in terms of the C language, there are no constraints on behavior. Sure, you might get a result one way on one implementation, but if you rely on that you're technically writing a dialect of C, and need to let the compiler know using flags.
Legalistically, I guess it could (the behavior isn't defined, after all), but typically the optimizer makes some valid-only-if-the-code-is deductions and things snowball from there....
This used to be the case. Signed integer overflow for instance is undefined because some CPUs go bananas when you try that. Other platform performed 2's complement just fine, and we used to be able to rely on this.
No longer.
See, the standard doesn't say "implementation defined". It doesn't say "undefined on platforms that go bananas, implementation defined otherwise". It says "undefined" period.
Signed integer overflow is undefined on all platforms, even your modern x86-64 CPU. Compiler writers interpreted it as a licence to assume it never happens, to help optimisations. For instance:
int x = whatever;
x += small_increment;
if (x < 0) { // check for overflow
abort(); // security shut down
}
proceed(x); // overflow didn't happen, we're safe!
Here's what the compiler thinks: int x = whatever;
x += small_increment;
if (x < 0) { // only true if signed overflow -> false
abort(); // dead code
}
proceed(x);
Then the compiler simply deletes your security check: int x = whatever;
x += small_increment;
proceed(x);
Don't listen to Chandler Carruth, nasal demons are real. Some undefined behaviours can encrypt your whole hard drive, assuming they're exploitable by malicious inputs.Now, if you used
unsigned int x = whatever;
...
if(x < 0)
There would be an obvious case for removing that if. $ cat undefined.c
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
int x = INT_MAX;
if (x+1 > x) {
printf("%d > %d\n", x+1, x);
} else {
printf("overflow!\n");
}
}
$ gcc --version
gcc (Ubuntu 7.2.0-8ubuntu3.2) 7.2.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ gcc undefined.c && ./a.out
overflow!
$ gcc -O3 undefined.c && ./a.out
-2147483648 > 2147483647 $ cat undefined.c
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
int x = INT_MAX;
if (x+1 < 0) {
printf("%d < 0\n", x+1);
} else {
printf("overflow!\n");
}
}
$ gcc --version
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ gcc undefined.c && ./a.out
-2147483648 < 0
$ gcc -O3 undefined.c && ./a.out
overflow!
The security check is gone all the same.There are cases where the compiler removes misguided overflow checks, since "perform UB, then check whether it happened" doesn't actually work, but your example is not such a case.
This raises an interesting point, because in many cases, assuming 2's complement and wrapping around, checking for overflow after the fact, as opposed to preventing it from happening in the first place, is actually easier. (And actually works if you use the `-fwrap` flag.)
The right thing should be easier to do than the wrong thing. It's a shame this is not the case here.
This is entirely different than implementation defined in that a conforming compiler has to document the behavior they implement and do it consistently.