wgarvin wrote:bob wrote:syzygy wrote:bob wrote:Rein Halbersma wrote:bob wrote:
Code: Select all
#include <stdio.h>
int main()
{
int i, k = 0;
for (i = 1; i > 0; i += i)
k++;
printf("k = %d\n", k);
return 0;
}
Running gcc -O0 will produce 31 on architectures where an int is 32-bits. However, with gcc -O2 and higher, the compiler will recognize that "i += i" yields signed overflow UB. It will then eliminate the entire expression, and further optimize this code to an infinite loop.
Such a scenario is also possible to happen with your XOR trick on multicore machines. Compiler routinely optimize away UB code instructions. You need extra compiler instructions or code modifcations to get the compiler to do what you intended.
Don't know what planet you compile on. Here's that run on my macbook:
scrappy% cc -O -o tst tst.c
.scrappy% ./tst
k = 31
scrappy% cc -O2 -o tst tst.c
scrappy% ./tst
k = 31
scrappy% cc -O3 -o tst tst.c
scrappy% ./tst
k = 31
gcc version 4.7.3 (MacPorts gcc47 4.7.3_3)
Online example using gcc -O3 (gcc 4.6.4 on Linux)
So? 4.7.3 newer than 4.6.4... perhaps they realized such an optimization is a mistake.
On my machine both 4.7.2 and 4.8.1 generate an infinite loop when compiling with -O3.
I gave the compile commands for 4.7.3 (macports) on my mac. And for the other versions on various departmental machines. One version of Intel C++ produced an infinite loop. Two others worked fine. So all I can confirm for failing is one specific version of intel C++. There may be several that fail. But there are some that work. I seem to be lucky enough to have more of those that work.
Why do you say that one compiler "fails" and another one "works". The program invokes undefined behavior. The language standard defines no semantics for it at all. All of those compilers are working as intended.
You seem to think you are "lucky" because you have a compiler that produces code from this snippet that matches your intuition about what the hardware would do if the compiler generated the code you imagine it would generate. But none of these compilers are obligated to generate that code (not even the ones that apparently do anyway). A future version of them might not generate that code. They might not generate that code next week, or when you compile a version of the function that has something else next to this snippet (e.g. different control flow, or whatever). In short, there's precious little you can rely on once you invoke undefined behavior, and the way these compilers treat this one code snippet doesn't really give you any guarantees about how they will treat other pieces of code. For that you have to rely on compiler-specific options, such as -fwrapv.
(And yes, I love being able to disable strict aliasing on all of the compilers I use... I hope that all major compilers continue to support that effectively forever, because there are billions of lines of code out there that don't respect the rule, and millions of programmers who don't know it well enough to follow it 100% of the time, and I am probably one of them).
Since one of the ongoing quibbles here is about the definition of "undefined behavior", here is the usual definition. One that has been used since the 1950's in fact. "Behavior that can not be predicted." This has ALWAYS been an issue of unexpected results caused, NOT by the compiler intentionally doing something off-the-wall, but by the code the compiler produces, which might not always do the expected thing.
The strcpy() function is an example. Since the first version of strcpy() it has copied left to right, because strings are terminated on the right with a NULL. The behavior has been undefined when the source/dest overlap because it is impossible to predict exactly what will happen if this is done inappropriately. For example, on a Cray, one can't copy a byte. Memory is 64-bit word addressable, only. If you want to implement strcpy() as a left-to-right byte copy, you can. You read a word, mask out one byte. Insert from source. write word back to memory. Read same word again, mask out next byte, insert, rewrite. 8 writes per word. Or you can do as the Cray Library did, and move 8 byte chunks left to right, but carefully checking for the zero terminator each time. This behaves differently than a byte by byte copy, but if you overlap the right direction, both work, if you overlap the wrong direction, both fail. But they fail differently. If someone wants to safely rely on that specific undefined behavior, look at the lib source. See what it does. If it fits your requirements and will produce what you expect, fine. It won't be portable. But it can be safely used. This "new definition" is nonsense. That a compiler can do ANYTHING it wants. Try to execute it, throw it out, generate a random number or whatever else it chooses. I do not consider that acceptable compiler behavior. Yes, it might discourage the use of undefined behavior. So will a shot to the head discourage speeding. Might be a bit draconian however.
This implies that a compiler writer might change this just for the hell of it. Ask yourself a real question, "Have I EVER changed something just to change it? Or did I change it to make it faster, more accurate, easier to read, more portable, or some other actual reason for changing the thing?" I know what my answer is.
Now let's take strcpy(). overlapping strings in one direction is clearly not going to work. Well-documented for 40+ years now. The other direction has also been well-documented to work for 40+ years. One might imagine that quite a few folks either intentionally or unintentionally used the "good direction" and got away with it. Anyone using the "bad direction" will have frequent failures and have to fix it.
Suddenly Apple decides to become the "undefined operation police" and they decide to focus specifically on strcpy(). The decide to abort the code whenever an overlap occurs, whether it is in the good direction or the bad direction.
What is the gain?
New programmers will be forced to not have overlapping strings, whether the strcpy() would actually work correctly or not. OK, not a bad result since there is inherent danger, particularly if one is not careful.
What is the loss?
Thousands of software packages suddenly fail. Software that has been purchased. Software than has been locally written. Software that has been downloaded. Tens of thousands of hours of development time are spent finding this unexpected and unexplained failure.
which is more important? I don't think you break working code to prevent future misuse.
One could just fix strcpy() so that it works for overlapping strings. I mean, the standard does NOT dictate that the function must use an implementation that is undefined for overlapping strings. This way you get your cake AND eat it too. You've eliminated the bug in future programs. You have fixed all existing software that is recompiled (or it might just link to a shared library containing the new version depending on the system). You have broken absolutely nothing.
So how is their actual action better than just fixing the thing? Overhead, you say? Remember, there is overhead to detect the strcpy overlap in the first place since you have to find the length of the string to see if there is an overlap.
IMHO this was done poorly, when it could have been done in a way that breaks nothing and fixes any possible use, AND it would eliminate that silly "undefined behavior" crutch for that specific function. They took the worst possible action, when even leaving it alone was better and fixing it was best.
I've written my share of compilers over the years, starting with one for Basic in the 70's and it was NEVER my intent to break something to teach those damned presumptive programmers a lesson. My intent was always to make it is reliable and correct as humanly possible, and when I could not be certain, I would at least try to do the right thing if the hardware was capable (int overflow, which all modern hardware handles just fine, but which some C compilers seem to think they know better and decide to just throw it out, breaking things instead of doing the right thing and letting the hardware deal with it, which it can with perfect consistency...