C++ Preprocessor: The Code in the Middle - Conditional Compilation
(Page 4 of 6 )
One more way to use ‘#define’ is to declare that a particular token is defined. This token can be used in combination with the precompiler commands “#if”, “#elif”, “#else” and “#endif” to test whether or not the preprocessor should include the code that follows the directive.
To see if a token is defined you use “#if defined” or “#elif defined” and to see if a token is not defined you use “#if !defined” or “#elif !defined”. You can use the logical ‘&&’ and ‘||’ operators to concatenate instructions.
If for some reason you would like to remove a token from the table the preprocessor stores it in, you can undefine it with ‘#undef’.
A common use is to define a token DEBUG when compiling non-optimized code. If you use the Microsoft Developer Environment, a token _DEBUG is automatically defined for your project’s Debug configuration.
#include <stdio.h>
#define MYDEBUG
static int const MAX_STR_LEN 80;
int main(int argc, char const *argv[]) {
int anArray[MAX_STR_LEN];
for (int idx=0; idx<MAX_STR_LEN; ++idx) {
#ifdef MYDEBUG
(void)printf(“%d “, idx);
#endif
anArray[idx]=0;
}
}
First the precompiler runs into the ‘#include’ instruction, so it reads the ‘stdio.h’ file (which it can find using your project’s header include path) and pastes it into the temporary source file it is going to feed to the compiler. Next it encounters the ‘#define’ instruction and it stores the MYDEBUG token in a lookup table.
Finally when it comes across the ‘#ifdef’ instruction it checks whether MYDEBUG was defined in its lookup table and when this is the case it will continue to write everything it finds until the ‘#endif’ instruction into an intermediate source file.
When it cannot find the token it will check whether the next instruction is another comparison “#elif”, tells it to follow that route otherwise “#else” or that the end of the block was reached “#endif”.
If you look at the macros used to configure STL-Port (STL), Boost (BOOST) or ACE (ACE), things can become rather complex. Handy as macros can be, they do not make the code easer to read (and thus understand). Here is a snippet from the boost thread class:
class BOOST_THREAD_DECL thread : private noncopyable {
...
private:
#if defined(BOOST_HAS_WINTHREADS)
void* m_thread;
unsigned int m_id;
#elif defined(BOOST_HAS_PTHREADS)
private:
pthread_t m_thread;
#elif defined(BOOST_HAS_MPTASKS)
MPQueueID m_pJoinQueueID;
MPTaskID m_pTaskID;
#endif
bool m_joinable;
};
Because the macros are obfuscating which variables are needed here (because different platforms offer different facilities), it is easy to overlook that on Linux this would filter into:
private:
private:
pthread_t m_thread;
bool m_joinable;
};
When you want your code to be portable across different platforms, preprocessor macros will help you to maintain a single source tree. Unfortunately your eyes will have to deal with all possibilities, instead of the single one that your compiler might be choking on.
When there are so many trees it becomes difficult to see the forest.
Next: Inclusion Guards >>
More C++ Articles
More By J. Nakamura