21

How can I prevent GCC from eliminating the code inside if(0) block?

When I use Visual Studio, one of my debugging techniques is to put code like this in my program:

if (0)
    do_some_debug_printing_and_checking();

Then, when a breakpoint is hit, I click at the do_some_debug_printing_and_checking() line, select "set next statement" and force it to execute.

When I use gcc/gdb as a back-end, the "set next statement" does not work anymore, as GCC simply removes the code from inside the if(0) statement.

I am of course using the -O0 flag to disable optimization. I have also tried the -fno-dce -fno-tree-dce flags to disable dead code elimination explicitly, but it has no effect: the contents of if(0) is just not present in the binary file and I cannot use set next statement to jump into it.

Is there any good way to tell gcc to disable elimination of if(0) contents?

Edit:

Thanks for the "additional variable" workaround, however there are 2 things I don't like about it:

  1. It's still an extra line of code
  2. It won't be automatically optimized out when I build the release version and do want those debug things to disappear. Surely I can use #ifdef-s, but that's even more extra lines.

Really, there's absolutely no option to make GCC keep that dead code?

Ivan Shcherbakov
  • 2,063
  • 14
  • 23
  • 1
    I know this is not what you were asking, but try using a global variable set to false for your ifs. That way the compiler will not optimize your if statements. – Kristina Jul 01 '12 at 11:17
  • 12
    Why don't you simply `call do_some_debug_printing_and_checking()` in gdb when you want to run that function? – Mat Jul 01 '12 at 11:20

2 Answers2

12

The simplest thing to do is to make the check depend on (say) a variable with external linkage.

E.g.

extern bool debug;
if (debug)
    do_some_debug_printing_and_checking();

The somewhere at namespace scope:

bool debug = false;
CB Bailey
  • 755,051
  • 104
  • 632
  • 656
  • so that the compiler wouldn't assume that the variable's value didn't change between some two points – Kos Jul 01 '12 at 12:08
  • 3
    @Kos: But all that's needed here is to prevent the compiler eliminating the `if`. `volatile` would be redundant. – CB Bailey Jul 01 '12 at 12:48
  • I'm OK with such a redundancy, actually. And I still think it does matter – Kos Jul 01 '12 at 13:55
  • @Kos: Why does it matter, am I missing something? – CB Bailey Jul 01 '12 at 13:56
  • Isn't it otherwise safe for the compiler to assume that the variable didn't change, if the code between two ifs is known? See http://pastebin.com/ugweBG82 and outputs http://pastebin.com/F3jH6Fha and http://pastebin.com/PdwcUWEw ; here DEBUG is indeed checked twice but this still feels redundant – Kos Jul 01 '12 at 14:03
  • @Kos: The variable has external linkage. Any function anywhere in the program could potentially change it's value at any time. This means that the compiler can't discard the code protected by the if statement (unless it's doing some extreme link time analysis which could apply in the volatile case as well). – CB Bailey Jul 01 '12 at 14:11
  • @Kos: If there was a previous assignment *in the same module*, then the compiler could safely determine that the value has not changed. However, if there is no assignment in this module, then the compiler does not know the value of the `debug` object, and cannot optimize the branch away. – sfstewman Jul 01 '12 at 19:29
  • @Charles: not true. The compiler has access to the the code of any function in the compilation unit, as well as knows the behavior of intrinsics (including any standard library function that is implemented as a compiler built-in). Also, the compiler is forbidden to optimize away read access from `volatile`, even in the presence of 'extreme link time analysis': one of the use cases for this requirement is memory locations that are changed by things that aren't the program. –  Jul 01 '12 at 20:08
  • @CharlesBailey: There's no reason in principle why the compiler could see the variable never changes, it would just have to be done at link time. And with LTO, I don't see it too far-fetched (I don't know for certain though). Volatile is a much simpler solution, IMO. – GManNickG Jul 01 '12 at 20:23
  • @Hurkyl: I don't understand your argument. Exactly what do you think I've said that isn't true? If it's about optimizing access to a `volatile` object then note that if you have a `volatile` object but you are compiling a complete program and you don't specify to the linker how it's mapped to some external address then the implementation can still optimize access to it under the "as if" rule because the implementation can place that object anywhere it chooses and it then knows exactly where all the accesses to and from it can legally occur. – CB Bailey Jul 01 '12 at 20:23
  • @Hurkyl: If a variable is declared `extern`, and any external functions are used between its assignment and the branch, then the compiler cannot know that the value is still the same, and cannot optimize the branch away. It's generally, though perhaps not always, a safe assumption that this `debug` variable defined, declared `extern`, and initialized at the module level will have external functions called between its declaration and the `if` statement. – sfstewman Jul 01 '12 at 20:26
  • @GManNickG: I'm 99.9% certain that gcc doesn't do LTO in `-O0` mode if you don't ask it to. – CB Bailey Jul 01 '12 at 20:27
  • @GManNickG: Could such a link-time analysis be sure of this if two or more modules linked to the `extern` variable? Are there any compilers/linkers that perform such a complex analysis on the market? I'm genuinely curious about this... if so, I'm quite impressed at how far compiler optimizations have come! – sfstewman Jul 01 '12 at 20:28
  • @CharlesBailey: Oh, right. I just meant to clarify your initial statement was missing some qualifiers, like "In -O0 in this version of GCC...", which isn't too non-obvious but I think was the source of confusion between you and Kos. – GManNickG Jul 01 '12 at 20:29
  • @sfstewman: Sure, just check to see where the symbol is used and if that symbol is a write. If the write isn't guaranteed to be `false` on each of those, then the compiler cannot optimize it. But if there are no writes or if all the writes are guaranteed to be `false`, the variable will always be `false` so the if branch can be eliminated. I doubt any compilers do such a thing, though, because most people aren't creating file-scope variables for no reason. I was merely trying to clarify Kos's position that it's possible in principle. – GManNickG Jul 01 '12 at 20:31
  • @GManNicKG: Oh, I see. I wondered why there were so many comments about a comparatively simple "work around". – CB Bailey Jul 01 '12 at 20:32
  • @CharlesBailey: Yeah, just fear that the principle could be/is a real practice in compilers, I think. I don't think it is; like I said to sfstewman, it wouldn't be a good investment in development time for compiler writers to care about such a strange case, but who knows. I do find, in the end, that volatile solution is much simpler because it limits scope/maintenance. (And as a plus gets around this worry of smart compiler optimization.) – GManNickG Jul 01 '12 at 20:33
9

I would not rely on gcc compiler flags to do this. Compiler flags can change across versions of gcc, and do change across compilers. You may find yourself needing to debug the same code in six months in Visual C++...

@CharlesBailey makes a nice suggestion for how to do this with an extern variable. Here's one alternative that doesn't require a variable to be exposed to the entire module or kept in static storage.

Declare a temporary variable volatile in the scope of the if statement:

if (volatile bool dbg = false)
{
  do_some_debug_printing_and_checking();
}

This keeps the scope of the temporary variable quite narrow. The volatile qualifier does not let the compiler assume anything about the variable, or optimize the branch away.

One thing to keep in mind is that the variable is always allocated on the stack, and will be kept on the stack until the function exits. Both this approach and the extern approach should work, but have slightly different (and probably negligible) tradeoffs.

If you're willing to use macros to help solve this problem, then you can easily disable the temporary variable when your release your code into production:

#ifndef IS_DEBUGGING
#  define IS_DEBUGGING 0
#endif

#if IS_DEBUGGING
#  define TMP_DBG_FLAG volatile bool dbg_flag = false
#else
#  define TMP_DBG_FLAG false
#endif

Then declare your if statement as:

if ( TMP_DBG_FLAG )
{
  do_some_debug_printing_and_checking();
}

When you define IS_DEBUGGING to be 1, the local variable is created, declared volatile, and kept. When you define IS_DEBUGGING to be 0, the macro expands to the constant false and the compiler optimizes the branch away. Something very similar could be done for the extern approach, as well.

This few extra lines of code, but they're independent of the number of times you use TMP_DBG_FLAG. The code is also a lot more readable than using tons of ifdefs. The macro could be made a bit safer (by appending the value of __LINE__ to it), but this would require three macros, and is probably not necessary:

#if IS_DEBUGGING
// paste symbols 'x' and 'y' together
#  define TMP_DBG_FLAG_SYMCAT0(x,y) x ## y

// need one level of indirection to expand __LINE__...
#  define TMP_DBG_FLAG_SYMCAT(x,y) TMP_DBG_FLAG_SYMCAT0(x,y)

#  define TMP_DBG_FLAG volatile bool TMP_DBG_FLAG_SYMCAT(dbg_flag_,__LINE__) = false
#else
#  define TMP_DBG_FLAG false
#endif
sfstewman
  • 5,589
  • 1
  • 19
  • 26