views:

226

answers:

5

I am programming an algorithm that contains 4 nested for loops. The problem is at at each level a pointer is updated. The innermost loop only uses 1 of the pointers. The algorithm does a complicated count. When I include a debugging statement that logs the combination of the indexes and the results of the count I get the correct answer. When the debugging statement is omitted, the count is incorrect. The program is compiled with the -O3 option on gcc. Why would this happen?

+5  A: 

Always put your code through something like valgrind, Purify, etc, before blaming the optimizer. Especially when blaming things related to pointers.

It's not to say the optimizer isn't broken, but more than likely, it's you. I've worked on various C++ compilers and seen my share of seg faults that only happen with optimized code. Quite often, people do things like forget to count the \0 when allocating space for a string, etc. And it's just luck at that point on which pages you're allocated when the program runs with different -O settings.

Also, important questions: are you dealing with restricted pointers at all?

Matt
A: 

Sounds like something is accessing memory that it shouldn't. Debugging symbols are famous for postponing bad news.

Is it pure C or there's any crazy thing like inline assembly?

However, run it on valgrind to check whether this might be happening. Also, did you try compiling with different optimization levels? And without debugging & optimizations?

jweyrich
+1  A: 

Print out the assembly code generated by the compiler, with optimizations. Compare to an assembly language listing of the code without optimizations.

The compiler may have figured out the some of the variables can be eliminated. They were not used in the computation. You can try to match wits with the compiler and factor out variables that are not used.

The compiler may have substituted a for loop with an equation. In some cases (after removing unused variables), the loop can be replaced by a simple equation. For example, a loop that adds 1 to a variable can be replaced by a multiplication statement.

You can tell the compiler to let a variable be by declaring it as volatile. The volatile keyword tells the compiler that the variable's value may be altered by means outside of the program and the compiler should not cache nor eliminate the variable. This is a popular technique in embedded systems programming.

Thomas Matthews
There was a question yesterday about optimization breaking something that should've been `volatile`: http://stackoverflow.com/questions/2603833/does-armcc-optimizes-non-volatile-variables-with-o0
Fred Larson
+1  A: 

Most likely your program somehow exploits undefined behaviour which works in your favour without optimisation, but with -O3 optimisation it turns against you.

I had a similar experience with one my project - it works fine with -O2 but breaks with -O3. I used setjmp()/longjmp() heavily in my code and I had to make half of variables volatile to get it working so I decided that -O2 is good enough.

qrdl
A: 

Without code this is difficult, but here's some things that I've seen before.

Debugging print statements often end up being the only user of a value that the compiler knows about. Without the print statement the compiler thinks that it can do away with any operations and memory requirements that would otherwise be required to compute or store that value.

A similar thing happens when you have side effects included within the argument list of your print statement.

printf("%i %i\n", x, y = x - z);

Another type of error can be:

for( i = 0; i < END; i++) {
     int *a = &i;
     foo(a);
}
if (bar) {
     int * a;
     baz(a);
}

This code would likely have the intended result because the compiler would probably choose to store both a variables in the same location, so the second a would have the last value that the other a had.

inline functions can have some strange behavior or you somehow rely on them not being inlined (or sometimes the other way round), which is often the case for unoptimized code.

You should definitely try compiling with warnings turned up to the maximum (-Wall for gcc). That will often tell you about the risky code.

(edit) Just thought of another.

If you have more than one way to reference a variable then you can have issues that work right without optimization, but break when optimization is turned up. There are two main ways this can happen.

The first is if a value can be changed by a signal handler or another thread. You need to tell the compiler about that so it will know that any access to assume that the value needs to be reloaded and/or stored. This is done by using the volatile keyword.

The second is aliasing. This is when you create two different ways to access the same memory. Compilers usually are quick to assume that you are aliasing with pointers, but not always. Also, they're are optimization flags for some that tell them to be less quick to make those assumptions, as well as ways that you could fool the compiler (crazy stuff like while (foo != bar) { foo++; } *foo = x; not being obviously a copy of bar to foo).

nategoose