Department of Electrical and Computer Engineering
Dalhousie University

Using the C Processor

Dr. Larry Hughes


Introduction

When a C program is compiled, a number of steps take place: the first is a pre-compilation step, while the second is the actual compilation of the program. The pre-compilation step involves a pre-processor removing comments from the program and interpreting directives.

The pre-processor directives invoke certain actions or generate code for the second step. Compiler directives are commands prefixed with ‘#’.

The #include Directive

The #include directive allows the programmer to include external source modules or header files in a module. The directive should be put at the start of a module, before any references are made to labels inside included module. There are two formats:

#include "myfile.h"
#include <string.h> 

The first format ("myfile.h"), refers to the module myfile.h in the present directory; it is possible to include a path as part of the module’s name. The second format (<string.h>) refers to the usr/include library.

The #define Directive

The #define directive defines symbols and macros. The format of the directive is:

#define name replacement

where ‘name’ is the name of the symbol or macro and ‘replacement’ is the text to be substituted for ‘name’. For example:

#define TRUE	1
#define FALSE	0
#define LIMIT    25
#define MASK     0xff
#define VALUE    'w' 

The names (such as TRUE, FALSE, and LIMIT) are replaced in the program by the pre-processor:

main()
{
char data[LIMIT];  /* 'data' is an array of size LIMIT */
if ((data[3] & MASK) == VALUE)
then data[3] = 0; 
} 

is supplied to the compiler as:

main()
{
char data[25];
if ((data[3] & 0xff) == 'w')
data[3] = 0;
} 

It is common to write all defined symbols in UPPER case to distinguish them from other data structures.

The #define directive can be used for defining macros, for example:

#define FOREVER       for(;;) 

In this case, wherever FOREVER is found in the module, it will be replaced with ‘for(;;)’.

Macros can also take arguments that are used as part of the replacement text. For example, the following macro takes one argument (named ‘x’) and replaces it with ‘x*=2;’:

#define DOUBLE(x)      x *= 2;

When the pre-processor encounters DOUBLE, it expects an argument that it substitutes for ‘x’. For example:

DOUBLE(alpha)

is replaced with

alpha *= 2;

Clearly, the rules associated with C cannot be violated when programming with macros, so in the above example, ‘alpha’ must be defined. Similarly, it is not permissible to write:

DOUBLE(4)

since this would result in

4 *= 2;

Finally, it is possible to simply define a name without giving it a replacement value; for example:

#define DEBUG

This can be used with conditional inclusion (below).

It is possible to define names from the cc compiler on the command line using the -D compiler option:

cc -DDEBUG -DVALUE=’w’ test.c

the above compiler options are equivalent to the following #defines:

#define DEBUG
#define VALUE	'w'

The #undef Directive

Any defined name can be undefined using the #undef directive:

#undef DEBUG

This is often used with conditional inclusion (below).

The #if Directives (Conditional Inclusion)

Conditional inclusion is possible using the #if compiler directives. Conditional inclusion permits the programmer to instruct the pre-processor to generate code under certain conditions (for example, when debugging a program). For example, to track down a fault, it is possible to plant diagnostic statements throughout a program and then remove them once the fault has been found and corrected. The ‘danger’ with this approach to programming is that removing the debugging statements often results in new faults being added to the code.

An alternative is to leave the debugging statements in the program. However, the production version of the software should not run with the debugging statements. The #ifdef directive allows the programmer to conditionally include blocks of code; for example:

#ifdef DEBUG
printf("Inside test().  data1 %x data2 %x alpha %x\n", data1, data2, alpha);
#endif

The code between #ifdef and #endif is included only if DEBUG is defined (using #define). When DEBUG is not defined (either using #undef or simply not defining it), the code between #ifdef and #endif is not passed to the compiler.

Another directive, #ifndef, is used to determine if a symbol has not been defined. This is a useful directive to use in the creation of header files, since it can help avoid cyclic includes (that is one header file includes another, which includes the first). For example:

#ifndef STRING_H
#define STRING_H
...
#endif 

In this case, if STRING_H is not defined, the pre-processor proceeds, defining STRING_H and including anything up to the #endif. Should this module be included again, STRING_H has been defined, so that the pre-processor does not proceed into the conditional instructions.

In addition to #ifdef and #ifndef, the pre-processor also includes #if, #else and #elif (else if). In all cases, the #ifs must be ended with #endif:

#ifdef AS_INT
int a;
#elif AS_FLOAT
float a;
#else
char a;
#endif

#if and #elif can also work with standard C conditional expressions; for example:

#if (TEST == 3 || DEBUG)
...
#endif

In the above example, the code between #if and #endif is included if TEST equals 3 or DEBUG is defined.

Miscellaneous Pre-Processor Directives

There are several other pre-processor directives that you may encounter, these include:


© 2003 - Whale Lake Press