A means of controlling the generation of a computer program based upon conditions specified at the time it is compiled (translated from its source code into an intermediate form, or into a machine language that the computer can directly process).

Conditional compilation is used mainly for:

  • Isolating blocks of code dependent upon a particular platform and/or compiler, and providing alternate blocks of code when the dependency is not met,
  • Preventing the same code from being compiled more than once due to the code's source file being accessed more than once by the compiler (the famous #include guard), and
  • Turning various facilities in the source code (such as debug statements) on or off as the programmer wishes.
The most widely recognized form of conditional compilation arises from the introduction of particular compiler directives into a program's source code.
This, of course, means that the compiler must provide facilities to support it.

The best-known (and most widely copied) of these facilities are the family of #if compiler directives that are used in the C and C++ programming languages.

Conditional compilation in C/C++

Because the traditional C compiler, cc, implements conditional compilation facilities in its preprocessing phase (and because all C and C++ compilers must behave as if the preprocessing phase takes place separately), these facilities take the form of preprocessor directives: lines of code that begin with a # character and which are followed by a word naming the directive.

Blocks of C and C++ code that might be compiled, and also might not, are placed in between a #if directive and a #endif directive:

#if compile-time-expression
//
// some code
//
#endif

If, at the time the program is compiled, the compile-time-expression evaluates to a true or nonzero value, the compiler evaluates all of the code between the #if and the #endif.  If not, the lines between the #if and the #endif are skipped.

Variations on the basic theme.

The directive #else is used when code also exists for those occations when the compile-time-expression evaluates to false.
so instead of

#if compile-time-expression
//
// some code
//
#endif
#if negative-form-of-above-compile-time-expression
//
// some more code
//
#endif

we have

#if compile-time-expression
//
// some code
//
#else
//
// some more code
//
#endif

which is easier to understand, and less error-prone.

The directive #elif combines a #else directive and the #if directive of a new condition.  It is not very useful, for reasons we will describe below.



#if blocks may be nested: That is, the lines between a #if ... #endif pair may contain a block of lines between another  #if ... #endif pair.   Thus:

#if foo
//
// some code
//
#  if bar
    //
    //  some more code.
    //  This will be compile only if both foo and bar are true.
    //
# endif  // to balance #if bar
//
// yet more code in the #if foo block
//
#endif // to balance #if foo



The basic #if directive has two very important variations:

#ifdef macro evaluates to a true value only if the specified preprocessor macro is defined when the compiler reaches the directive.

Similarly, #ifndef macro evaluates to a true value only if the specified macro is not defined.

Due to the nature of the primary uses of conditional compilation described above,  #ifdef and #ifndef are traditionally used much more frequently than #if  (the reason #elif isn't very useful).

This has recently changed in the C++ world.  Due to the complexity of the compile-time expressions that programmers found they had to use in C++, the construction defined(macro) was added to the preprocessor language.  This allows checking the definition / non-definition of more than one macro in the same expression.


Isolating platform dependencies

Platform dependencies are isolated by using #ifdef with a macro named for the platform.  These are either pre-defined in the compiler, or set in the makefile used to control compiling the entire project. Thus:

#ifdef _MSCVER
//
// This is Microsoft Visual C or Visual C++
//
#if _MSCVER > 600
  //
  // Visual C++ 6.0 or better
  //
# endif
#endif
#ifdef _BORLANDC_
//
// it's Turbo C or Borland C++ Builder
//
#endif
#ifdef __MWERKS__
//
// It's CodeWarrior
//
#endif
#ifdef POSIX
//
//  POSIX-compliant operating system
//
#endif
#ifdef __cplusplus
//
// it's C++ and not C
//
#endif

There are all sorts of constructs you can make, for the compiler version, processor version, operating system, and things like that.


Include guards

Include guards arise from the traditional use of include files to specify a C module's interface to the outside world.
Let's say you have a module foo whose interface you have packaged neatly into the file foo.h.   Let's say you also have two other modules, foo_user1 and foo_user2 which both depend on the module foo. You've packaged their interfaces into foo_user1.h and foo_user1.h:

// -- foo_user1.h --
#include "foo.h"
...
... foo_user1 interface

// -- foo_usr2.h --
#include "foo.h"
...
... foo_user2 interface

Finally, let's say you have a module bar which uses both foo_user1 and foo_user2, but doesn't need to know about foo's interface.   It has to include foo_user1.h and foo_user2.h:

// -- bar.c --
#include "foo_user1.h"
#include "foo_user2.h"

Since foo.h will be included more than once, the above construction will be rejected by the compiler somewhere in the bowels of foo.h the second time around, the moment something is defined the second time.   A solution might be to remove the references to foo.h from foo_user1.h and foo_user2.h

The solution is to make up a macro name for each file that might be #include'ed, and then to wrap the contents of the file in a #if construction like this:

#ifndef THIS_HERE_INCLUDE_FILE_H
#define THIS_HERE_INCLUDE_FILE_H
//
//  All the rest of the code in the file
//
#endif

The trick is to #define the macro so that the #ifndef directive can be true only once in a particular translation unit.

In this way, the contents of the include file will be processed the first time it is accessed because of a #include directive in another file.  If a later #include directive mentions the same file, however, the contents won't be processed again.

It has become fashionable in some circles to place the #include guard around the #include directive rather than inside the included file.  Don't do this. Although this technique reduces dependencies in automatic makefile generators, it obfuscates your code.

Another phenomenon you will run into frequently is the construction #pragma once, which is a facility provided by some compiler manufacturers (most notably Microsoft) to do precisely the same thing.  But it's a platform dependency, which you have to isolate with... conditional compilation.  Plus, you have to provide alternative code for platforms that don't support it, which means... an include guard.


Switching facilities on and off

During the programming cycle, you may have differing purposes for invoking a compiler:  At one point, you may wish to create a program you can debug; at other times, you may be finished debugging (ha!) and may wish to create a sleek, optimized program.  To that end, you might create the following construction:

#ifdef DEBUG
#pragma take_it_easy_on_me_O_mighty_compiler
#else
#pragma make_me_a_lean_mean_virtual_machine
#endif

Although symbolic debuggers are nice, sometimes nothing beats a good old print statement for diagnosing your problem.
You might then have:

#ifdef DEBUG
cout << "i = " << i << "; I hope you're satisfied." << endl
#endif

This, of course, has evolved into the famous ASSERT macro:

#ifdef _NDEBUG
#define ASSERT(cond) if (!cond) { cout << "Hey idiot, (" << ## cond << ") doesn't hold!" << endl; crash (); }
#else
#define ASSERT(cond)
#endif

If _NDEBUG is on, the program contains code to test the condition at the point of the assertion of the macro.
If the condition is false, the program crashes and an error message is printed.

In production code, you have assured youself that the condition will always (ha again!) hold.  Therefore, you turn _NDEBUG off, and the extra code doesn't appear in the final program.

Log in or registerto write something here or to contact authors.