The Preprocessor in C: Conditional Compilation

The C preprocessor  offers a feature known as conditional compilation. Conditional compi- lation is often used to create one program that can be compiled to run on different computer systems. It is also often used to switch on or off various statements in the pro- gram, such as debugging statements that print out the values of various variables or trace the flow of program execution.

1. The #ifdef, #endif, #else, and #ifndef Statements

You were shown earlier in this chapter how you could make the rotate function from Chapter 12 more portable.You saw there how the use of a define would help in this regard. The definition

#define kIntSize 32

was used to isolate the dependency on the specific number of bits contained in an unsigned int. It was  noted in several places that this dependency does not have to be made at all because the program can itself determine the number of bits stored inside an unsigned int.

Unfortunately, a program sometimes must rely on system-dependent parameters—on a filename, for example—that might be specified differently on different systems or on a particular feature of the operating system.

If you had a large program that had many such dependencies on the particular hard- ware and/or software of the computer system (and this should be minimized as much as possible), you might end up with many defines whose values would have to be changed when the program was moved to another computer system.

You can help reduce the problem of having to change these defines when the pro- gram is moved and can incorporate the values of these defines for each different machine into the program by using the conditional compilation capabilities of the pre- processor. As a simple example, the statements

#ifdef UNIX

#  define DATADIR “/uxn1/data”

#else

#  define DATADIR “\usr\data”

#endif

have the effect of defining DATADIR to “/uxn1/data” if the symbol UNIX has been previ- ously defined and to “\usr\data” otherwise. As you can see here, you are allowed to put one or more spaces after the # that begins a preprocessor statement.

The #ifdef, #else, and #endif statements behave as you would expect. If the sym- bol specified on the #ifdef line has been already defined—through a #define statement or through the command line when the program is compiled—then lines that follow up to a #else, #elif, or #endif are processed by the compiler; otherwise, they are ignored.

To define the symbol UNIX to the preprocessor, the statement

#define UNIX  1

or even just

#define UNIX

suffices. Most compilers also permit you to define a name to the preprocessor when the program is compiled by using a special option to the compiler command. The gcc com- mand line

gcc -D UNIX program.c

defines the name UNIX to the preprocessor, causing all #ifdef UNIX statements inside program.c to evaluate as TRUE  (note that the -D UNIX must be typed before the pro- gram name on the command line). This technique enables names to be defined without having to edit the source program.

A value can also be assigned to the defined name on the command line. For example,

gcc -D GNUDIR=/c/gnustep program.c

invokes the gcc compiler, defining the name GNUDIR to be the text /c/gnustep.

2. Avoiding  Multiple Inclusion of Header Files

The #ifndef statement follows along the same lines as the #ifdef. This statement is used the same way the #ifdef statement is used, except that it causes the subsequent lines to be processed if the indicated symbol is not defined. This statement is often used to avoid multiple inclusion of a file in a program. For example, inside a header file, if you want to make certain it is included only once in a program, you can define a unique identifier that can be tested later. Consider the sequence of statements:

#ifndef _MYSTDIO_H

#define _MYSTDIO_H

#endif /* _MYSTDIO_H */

Suppose you typed this into a file called mystdio.h. If you included this file in your pro- gram with a statement like this:

#include “mystdio.h”

the #ifndef inside the file would test whether _MYSTDIO_H were defined. Because it wouldn’t be, the lines between the #ifndef and the matching #endif would be includ- ed in the program. Presumably, this would contain all of the statements that you want included in your program from this header file. Notice that the very next line in the header file defines _MYSTDIO_H. If an attempt were made to again include the file in the program, _MYSTDIO_H would be defined, so the statements that followed (up to the #endif, which presumably is placed at the very end of your header file) would not be included in the program, thus avoiding multiple inclusion of the file in the program.

This method as shown is used in the system header files to avoid their multiple inclu- sion in your programs. Take a look at some and see!

3. The #if and #elif Preprocessor Statements

The #if preprocessor statement offers a more general way of controlling conditional compilation. The #if statement can be used to test whether a constant expression evalu- ates to nonzero. If the result of the expression is nonzero, subsequent lines up to a #else,

#elif, or #endif are processed; otherwise, they are skipped. As an example of how this might be used, assume you define the name OS, which is set to 1 if the operating system is Macintosh OS, to 2 if the operating system  is Windows, to 3 if the operating system is Linux, and so on.You could write a sequence of statements to conditionally compile statements based upon the value of OS as follows:

#if   OS == 1 /* Mac OS */

#elif OS == 2 /* Windows */

#elif OS == 3 /* Linux */

#else

#endif

With most compilers, you can assign a value to the name OS on the command line using the -D option discussed earlier. The command line

gcc -D OS=2 program.c

compiles program.c with the name OS defined as 2. This causes the program to be com- piled to run under Windows.

The special operator

defined (name)

can also be used in #if statements. The set of preprocessor statements

#if defined (DEBUG)

#endif

and

#ifdef DEBUG

#endif

do the same thing. The statements

#if defined (WINDOWS) || defined (WINDOWSNT)

# define BOOT_DRIVE “C:/”

#else

# define BOOT_DRIVE “D:/”

#endif

define BOOT_DRIVE as “C:/” if either WINDOWS or WINDOWSNT is defined and as “D:/” otherwise.

4. The #undef Statement

On some occasions, you might need to cause a defined name to become undefined. This is done with the #undef statement. To remove the definition of a particular name, you write

#undef name

So the statement

#undef WINDOWS_NT

removes the definition of WINDOWS_NT. Subsequent #ifdef WINDOWS_NT or #if defined (WINDOWS_NT) statements will evaluate as FALSE.

This concludes the discussion of the preprocessor.You have seen how the preproces- sor can be used to make programs easier to read, write, and modify.You’ve  also seen how you can use include files to group common definitions and declarations together into a file that can be shared among different files. Some other preprocessor statements that weren’t described here are described in Appendix A, “C Language Summary.”

In the next chapter, you’ll learn more about data types and type conversions. Before proceeding, try the following exercises.

Source: Kochan Stephen G. (2004), Programming in C: A Complete Introduction to the C Programming Language, Sams; Subsequent edition.

Leave a Reply

Your email address will not be published. Required fields are marked *