Previous: 3.3.3 Including Header Files
Up: 3.3 Coding Programs for Readability
Previous Page: 3.3.3 Including Header Files
The third useful facility provided by the preprocessor is conditional compilation; i.e. the selection of lines of source code to be compiled and those to be ignored. While conditional compilation can be used for many purposes, we will illustrate its use with debug statements. In our previous programming examples, we have discussed the usefulness of printf() statements inserted in the code for the purpose of displaying debug information during program testing. Once the program is debugged and accepted as ``working'', it is desirable to remove these debug statements to use the program. Of course, if later an undetected bug appears during program use, we would like to put some or all debug statements back in the code to pinpoint and fix the bug. One approach to this is to simply ``comment out'' the debug statements; i.e. surround them with comment markers, so that if they are needed again, they can be ``uncommented''. This is a vast improvement over removing them and later having to type them back. However, this approach does require going through the entire source file(s) to find all of the debug statements and comment or uncomment them.
The C preprocessor provides a better alternative, namely conditional compilation. Lines of source code that may be sometimes desired in the program and other times not, are surrounded by #ifdef, #endif directive pairs as follows:
#ifdef DEBUG printf("debug:x = %d, y = %f\n", x, y); ... #endifThe #ifdef directive specifies that if DEBUG exists as a defined macro, i.e. is defined by means of a #define directive, then the statements between the #ifdef directive and the #endif directive are retained in the source file passed to the compiler. If DEBUG does not exist as a macro, then these statements are not passed on to the compiler.
Thus to ``turn on'' debugging statements, we simply include a definition:
#define DEBUG 1in the source file; and to ``turn off'' debug we remove (or comment) the definition. In fact, the replacement string of the macro, DEBUG is not important; all that matters is the fact that its definition exists. Therefore,
#define DEBUGis a sufficient definition for conditional compilation purposes. During the debug phase, we define DEBUG at the head of a source file, and compile the program. All statements appearing anywhere between #ifdef and matching #endif directives will be compiled as part of the program. When the program has been debugged, we take out the DEBUG definition, and recompile the program. The program will be compiled excluding the debug statements. The advantage is that debug statements do not have to be physically tracked down and removed. Also, if a program needs modification, the debug statements are in place and can simply be reactivated.
In general, conditional compilation directives begin with an if-part and end with an endif-part. Optionally, an else-part or an elseif-part may be present before the endif-part. The keywords for the different parts are:
We have already discussed the keyword ifdef. The keyword ifndef means ``if not defined''. If the identifier following it is NOT defined, then the statements until the next <else-part>, <elseif-part> or <endif-part> are compiled.
The keyword if must be followed by a constant expression, i.e. an expression made up of constants and operators. If the constant expression is True, the statements until the next else-part, elseif-part or endif-part are compiled. In fact, the keyword ifdef is just a special case of the if form. The directive:
#ifdef identis equivalent to:
#if defined ident
We can also use #if to test for the presence of a device, for example, so that if it is present, we can include an appropriate header file.
#if DEVICE == MOUSE #include mouse.h #endifHere, both DEVICE and MOUSE are assumed to be constant identifiers.
The #elif provides a multiway branching in conditional compilation analogous to else ... if in C. Suppose, we wish to write a program that must work with any one of a variety of printers. We need to include in the program a header file to support the use of a specific printer. Let us assume that the specific printer used in an installation is defined by a macro DEVICE. We can then write conditional compilation directives to include the appropriate header file.
#if DEVICE == IBM #include ibmdrv.h #elif DEVICE == HP #include hpdrv.h #else #include gendrv.h #endifOnly constant expressions are allowed in conditional compilation directives. Therefore, in the above code, DEVICE, IBM, and HP must be be defined constants.
Using compiler directives is a convenience for the programmer and makes program source files easier to understand. One goal in understandable files is to make them small, the less a reader has to look at in trying to understand a program, the better. Good programming style includes the hiding of details at the algorithm level with functions, at the source code level using macros, and at the source file level using header files and conditional compilation. One comment should be made about header files. The information stored in header files is meant to be directives and prototype statements, NOT code statements or function definitions. Also DO NOT:
#include "somefile.c"The syntax of the #include directive allows these, but it is considered bad style. A final version of our file niceday.c using these compiler directives is shown in Figure 3.11.