conditional compilation (idea)
Return to conditional compilation (idea)
|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:
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, 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
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:
The basic #if directive has two very important variations:
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 dependenciesPlatform 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:
There are all sorts of constructs you can make, for the compiler version,
processor version, operating system, and things like that.
Include guardsInclude 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 --
// -- foo_usr2.h --
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 --
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:
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 offDuring 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:
This, of course, has evolved into the famous ASSERT macro:
If _NDEBUG is on, the program contains code to test
the condition at the point of the assertion of the macro.
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.