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
void HandleResponse(const CHTTPResponse& resp) {
#ifdef WITH_CONTENT_VALIDATOR
if (!Validate(resp.content(CHTTPResponse::Text))) {
ForwardCorruptedResponse(resp);
}
#endif
// ...
}
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 | FALSE | FALSE |
-DTEST | TRUE | TRUE | TRUE |
-DTEST=0 | TRUE | TRUE | FALSE |
-DTEST=1 | TRUE | TRUE | TRUE |
-DTEST=FOO | TRUE | TRUE | FALSE |
-DTEST=FOO -DFOO=1 | TRUE | TRUE | TRUE |
-DTEST=FOO -DFOO=0 | TRUE | TRUE | FALSE |
-DTEST=FOO -DFOO | TRUE | TRUE | TRUE |
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.