This time I would like to focus on some aspect that every C/C++ programmer is familiar with. That is preprocessor conditions. Before being crucified because of using #ifdef and C++ in the same sentence. I would like to confess, that this is still something frequently used.

What is #ifdef?

#ifdef is a preprocessor condition, that includes containing code whether condition predicate solves to ‘true’. It might be useful to make sure some code will be never compiled.

For the instance let’s see a code

So far so good, let’s just compile our project with: g++ -DWITH_CONTENT_VALIDATOR=1 and see if everything works. It does. Sweet! No tests are required now, so let’s disable them by setting -DWITH_CONTENT_VALIDATOR=0 and check. Wait! My tests are still executed!

What’s the problem?

The problem is mainly with a way of passing ‘value’ of those macros to preprocessor/compiler. I’ve seen in our projects several different ways of doing that:

  • -DWITH_CONTENT_VALIDATOR
  • -DWITH_CONTENT_VALIDATOR=1
  • -DWITH_CONTENT_VALIDATOR=0 – if somebody would like to disable it.

The last case is problematic, if person that doesn’t know implementation details sees that WITH_CONTENT_VALIDATOR was defined with value 1 it can be natural to set 0 in order to disable this functionality. But this is not how #ifdef works. #ifdef checks what is stands for. It checks “if defined”, and the macro is defined, to 0, but it’s defined!

Alternatives

C preprocessor let’s you to include a code conditionally in following ways:

  • #ifdef – tests whether given macro name is defined or not – doesn’t care about value of it
  • #if defined() – the same like #ifdef
  • #if – checks if given macro name resolves to true, and this could be achieved by:
    • having a numeric, non zero value
    • being defined without any value
    • being defined to another macro name that meets one of this condition
compiler flag \ test
#ifdef TEST
#if defined(TEST)
#if TEST
[no macro defined]FALSE
FALSEFALSE
-DTESTTRUETRUETRUE
-DTEST=0
TRUETRUEFALSE
-DTEST=1
TRUETRUETRUE
-DTEST=FOO
TRUETRUEFALSE
-DTEST=FOO -DFOO=1TRUETRUETRUE
-DTEST=FOO -DFOO=0TRUETRUEFALSE
-DTEST=FOO -DFOOTRUETRUETRUE

As you see, there is no difference between #ifdef and #if defined, but it’s a difference in case of #if. For the instance when TEST is defined to 0, then #ifdef resolves this positively, but #if checks a value, and resolves this negatively.

Conclusion

I’ve found that developers tends to use #ifdef, #if defined and #if interchangeably, where those preprocessor conditions have different purpose and they work differently. In majority of cases when a value of macro is defined externally, in build configuration that is passed to build system – it’s used to enable/disable some specific functionality. In those cases without knowing implementation we might achieve result different from intention when we set a value to 0. Because of that, I believe that we should test those flags with #if FLAG statements. 

 
2 Kudos
Don't
move!