rbarreira wrote:bob wrote:
That begins to sound like either a variable that is uninitialized, or a local array subscript that goes out of bounds. If a printf fixes the problem, the only change a printf has is that it will alter the stack since it is a library call. And altering the stack can change a value you might get on a bad array index.
I disagree. Adding printfs (or anything really) can trigger all sorts of conditions on the compiler which may obscure the bug in many different ways.
A few examples of what adding a simple printf can affect:
1- It may change the register allocation behavior.
2- It affects the code size, potentially triggering different heuristic behavior for the inlining of functions.
3- It may cause a spill of a value to memory which fixes the bug.
4- It may affect the instruction ordering. (instruction ordering bugs are quite difficult to reproduce while one is changing the code)
When I find what appears to be a compiler bug, I try to isolate and reduce the bug-causing code as much as possible. Otherwise it's a nightmare to look through the assembly code, and no compiler vendor would take my bug reports seriously if I didn't produce a small test case (since I'm not paying big bucks for intensive product support).
Adding printf can also change inlining decisions in the surrounding code. Since turning off inlining hides the bug, and the bug is for a recent version of a 64-bit compiler, I think its at least plausible that it is a compiler bug.
Since he has actually reduced the code to something fairly small already, I hope Fritz will post the assembly that the compiler generates so that we can have a look at it. It might not help, or there might turn out to be an obvious bug in the generated code.
Just last year I saw a compiler bug in an MS compiler where it had an inlined function which was passed a reference to a primitive int and decremented it (it was something to do with reference counting). Its been a while and I forget the details, but based on the surrounding code, the compiler decided that the value was a constant and completely replaced the read-decrement-write logic with an instruction that stored a constant into the variable, causing a slow memory leak in our application *only in our LTCG-optimized builds*. Since the if-expression became a "constant", the code for that condition also got optimized out. Imagine our surprise when we looked at the generated code for an inlined "AddRef/Release" and discovered that the "Release" did nothing but store 1 into the reference count! Another instance of the same bug caused the app to crash by calling the wrong cleanup function on a data structure, because it generated bad code in the function which figured out which cleanup function to apply, and that one occurred in all of our builds except the debug ones. I think we worked around these bugs by putting our "noinline" macro on the methods involved.
Anyway... if changing relatively safe compiler options (like inlining) can change the program's behaviour, then you have only three choices:
(1) You're relying on undefined behaviour, which is always bad. Breaking aliasing rules or pointer arithmetic/casting rules are common examples. Reading uninitialized variables or trashing your stack due to a buffer overrun would also fall into that category. Technically if you invoke undefined behaviour, the compiler can do anything it wants including printing "PIGEON!" in giant letters and then exiting. Some compilers will even generate code to deference a constant NULL pointer ("crash now").
(2) You're relying on implementation-defined behaviour, which is not as bad as undefined but is still worth avoiding if you can (but there are many things here which are safe on all reasonable implementations, such as memsetting pointers with zero bytes, or relying on integer arithmetic being 2's complement). Or..
(3) The code is correct and its a genuine compiler bug! The rarest of the three, but it does occasionally happen.