|
I think there is an important point here, which is that C and C++ compilers have let us get away with a lot of undefined behavior for a long time, and that there hasn't been a lot of tooling to help avoid it nor a culture that stresses the long-term danger of depending on it. I can speak as someone who has been programming in C and C++ for over ten years, but only in the last few years became aware of this issue and started taking it seriously. Five years ago I would do things like cast function pointers to void-pointer and back, or calculate addresses that were outside the bounds of any allocated object and compare against them, all without really even realizing I was doing something wrong. I don't think this will spell doom-and-gloom for C and C++ though. I think a few things will happen. First of all, the compiler people are walking a fine line; yes, they are breaking code that relies on undefined behavior, but they often avoid breaking too much. For example, I've had it explained to me that at least for the time being, gcc's LTO avoids breaking any programs that would work when compiled with a traditional linker. In addition, they often provide switches that preserve traditional semantics for non-compliant code that needs it (like -fno-strict-aliasing and -fwrapv). Secondly, I believe that tooling will get better, and rather than ignoring the warnings I believe that people's general awareness of this issue will raise, as well as knowledge of standard-compliant ways of working around common patterns of undefined behavior. For example, it's often easy to avoid aliasing problems by using memcpy(), and this can usually be optimized away. Thirdly, I expect that the standard may begin to define some of this behavior. For example, I think that non-twos-complement systems are exceedingly rare these days; I wouldn't be surprised if a future version of the standard defines unsigned->signed conversions accordingly. |
unsigned -> signed conversion is already “implementation-defined behavior” (as opposed to “undefined behavior”). The standard does not guarantee how it behaves but forces compilers to make a choice and to stick to it.
A different example, of a behavior that is really undefined, would be signed arithmetic overflow:
int detect_max(int x) { return x+1 < x; }
The function above branchlessly detects that its argument is INT_MAX, and returns 1 in this case thanks to 2's complement representation.
Except that it doesn't. The command “gcc -O2” compiles it into “return 0;”. GCC can do this, because signed arithmetic overflow is undefined behavior. The compiler is only taking advantage of undefined behavior in a way locally convenient.
Now that two's complement is (almost) everywhere, making it the standard for signed arithmetic overflows is the sort of bold choice I would like to see, but it won't happen (it would break GCC's existing optimization).