2. C Preprocessor Directives
Introduction and Motivation
The first step in compiling any C program is the preprocessor, a sort of
automated editor that modifies (a copy of ) the source code before passing it
on to the compiler to translate into machine language code.
One of the tasks of the preprocessor is to strip off all comments, which the
compiler ignores.
The preprocessor also responds to directives in the code, which give the
preprocessor explicit instructions on how to edit the source code before
passing it on to the compiler.
The #include Preprocessor Directive
The #include directive causes the preprocessor to read in the contents of
some other file, which may in turn #include some other file(s), etc.
Motivation:
Small programs, containing only a main( ) function and few if any
additional function can be kept all in one file.
For programs of any significant size, however, it is better to break the
program up into separate files. Some of the advantages of this approach are:
o When changes are made to part of the program, it is only necessary to
recompile the file(s) that have been changed. Any other files can be
re-used without having to be recompiled.
o Certain functions and their special data types may be used by more
than one program. This is much easier if these functions and their
data types are defined in their own files, separate from main( ).
These routines can be inserted into libraries, and/or distributed
to others in binary compiled format.
Application of #include
The preprocessor reads C program source code files and performs some
preliminary processing on them before passing along the modified versions
to the full compiler.
In particular, the #include directive tells the pre-processor to go read in the
contents of a particular file, and place the contents inside the file being
compiled at this point. The effect is as if you copied one file and pasted it
into the other. (Note that the original file is not actually changed. A
temporary copy is made, modified by the preprocessor, and then passed
3. along to the compiler. The compiler itself never sees the unmodified
original. )
Most commonly the #inlcuded files have a ".h" extension, indicating that
they are header files.
There are two common formats for #includes, as follows:
o #include < libraryFile.h > // The angle brackets say to look in the
standard system directories
o #include " personalHeaders.h" // The quotation marks say to look in
the current directory.
Directories and disk drive information is legal, but discouraged since it is
not portable:
o #include <C:Program FilesLibrariesIncludessomefile.h > // Too
specfiic
o #include <sys/types.h> // Common subdirectory, relative to the
standard locations.
Typical Contents of #included Files
1. Defined Types and Data Structures
structs
enums
typedefs
2. Defined Constants
The #define preprocessor directive can be used to globally replace a
word with a number. (i.e., text substitution)
It acts as if an editor did a global search-and-replace edit of the file.
So for example, if you have:
#define MAXARRAYSIZE 100
then everywhere that the preprocessor finds MAXARRAYSIZE, it will
replace it with 100. So:
int numbers[ MAXARRAYSIZE ];
for( int i = 0; i < MAXARRAYSIZE; i++ )
become:
int numbers[ 100 ];
for( int i = 0; i < 100; i++ )
4. Beware of two common errors with #defines - Adding extra characters
that don't belong. For example:
#define MAXARRAYSIZE = 100
#define MAXARRAYSIZE 100;
results in :
int numbers[ = 100 ];
int numbers[ 100; ];
Leading to the unexpected error messages:
o Error: = not expected
o Error: missing ]
Preprocessor defines can incorporate previously defined variables, as in:
#define PI 3.14159
#define PI2 ( PI / 2.0 )
3. Function Prototypes
Adding function prototypes into header files ensures that all functions
agree on what the prototype should be, and if it changes, it only needs
to be changed in one place.
4. Declared ( not Defined ) Global Variables
The "extern" keyword on a global variable declaration lets the compiler
know that the variable exists, but does not allocate any space for it.
Exactly one file in the program must define the variable ( allocate space
and initialize if appropriate ), by declaring it without the extern
keyword.
So in the .h file that is #included by everybody:
extern int maxAssignments, maxScores[ ]; // Note no size given
for the array
and then in exactly one .c file:
int maxAssignments = 7, maxScores[ maxAssignments ]; // Here a
size is required and initialization is allowed Macros
5. Avoiding Circular Includes
#included header files often #include other header files in a daisy-chain like
manner.
In order to avoid an infinite cycle of circular includes, the pre-processor
directives #ifndef, #define, and #endif are often used. Consider this
example for the header file named "headerFile.h":
#ifndef HEADERFILE_H
#define HEADERFILE_H
// Normal Header File Contents
// May #include other files, which may #include this one
eventually
#endif
The #define Preprocessor Directive
The #define directive is used to "define" preprocessor "variables", which can
then be used in one of three ways. Text substitution is already discussed
above. The rest two ways are shown in the following two sections.
Using #define For Boolean Testing
Sometimes the preprocessor only needs to know whether a particular
preprocessor variable is defined or undefined.
In this case, use #define and #undef to turn such variables "on" or "off"
The status of a preprocessor variable can be tested with #ifdef or #ifndef,
either of which starts a block of code that must be terminated by a matching
#endif. ( #elif and #else can also be used, if you need more complicated if-
else type blocks. )
For example, you may have a number of printing statements that you only
want active when debugging. You can "protect" them in a "ifdef" block as
follows:
#ifdef DEBUG
printf( "Now beginning loop %d with x = %f, y = %f, and z =
%fn", i, x, y, z );
#endif
Then you can turn all such blocks on or off at once using
6. #define DEBUG
or
#undef DEBUG
Boolean type preprocessor variables can be defined from the UNIX
command-line using the "-D" flag to gcc, i.e. "-DDEBUG".
Using #define To Define Macros
In addition to simple variable substitutions, it is also possible to create pre-
processor macros that take arguments.
Macros operate much like functions, but because they are expanded in
place they are generally faster, since they do not invoke the overhead of
transferring control over to a function and then transferring control back
again.
However there are some potential dangers when using macros instead of
functions, as described below.
Example:
#define PI 3.14159
#define SQUARE(x) x * x
...
area = PI * SQUARE( radius );
which will be seen by the compiler as:
area = 3.14159 * radius * radius;
Note: It is very important to include parentheses around all variables used in a
preprocessor macro, as well as around the entire definition.
Exercise: What will be the effects of the following? Which is correct?
A. #define SQUARE(x) x * x
B. ...
C. abSquared = SQUARE( a + b );
D. #define SQUARE(x) (x) * (x)
E. ...
F. a_over_b_squared = a / SQUARE( b );
G. #define SQUARE(x) ( (x) * (x) )
H. ...
I. answer = SQUARE( a + b ) / SQUARE( b );
7. Landmine! Do not use auto-increment or decrement operators, assignment
statements, or function calls in combination with preprocessor
macros. Consider:
for( i = 0; i < 10; )
printf( "%d squared = %dn, i, SQUARE( i++ ) );
Preprocessor macros can take multiple arguments:
#define A_MOD_B(a,b) ( (a) % (b) )
Preprocessor macros can also make use of previously defined macros
#define MAX(a,b) ( (a) > (b) ? (a) : (b) )
#define MAX3(a,b,c) ( ((a) > (b)) ? MAX((a),(c)) : MAX((b),(c))
)
// or #define MAX3(a,b,c) ( MAX((a), MAX((b),(c)) )
Preprocessor macros can span multiple lines only if the end of line is explicitly
escaped:
#define MIN(a,b) ( (a) < (b) ?
(a) :
(b) )
Note that macros do not check the types of their arguments, which can have
both good and bad consequences.
Predefined Macros
There are a number of macros automatically pre-defined by the
compiler, inlcuding the following list. Note that each one begins and
ends with TWO underscore characters:
o __LINE__ - The line number of the current file being
compiled
o __FILE__ - The name of the current file being compiled
o __DATE__ - The current date
o __TIME__ - The current time
The #pragma Preprocessor Directive
The #pragma directive is used to give the preprocessor ( and compiler
) specific details on exactly how to compile the program. For
example, specific compiler warnings can be ignored, or warning
levels changed up or down for different sections of code.
Examples:
o #pragma page( ) // Forces a form feed in the listing
8. o #pragma warning( disable: 2044 ) // Disables warning number
2044
o #pragma line 100 // Sets the current line number to 100 for
listing and reporting purposes.
Header Files
A header file is a file with extension .h which contains C function declarations
and macro definitions to be shared between several source files. There are two
types of header files: the files that the programmer writes and the files that
comes with your compiler.
You request to use a header file in your program by including it with the C
preprocessing directive #include, like you have seen inclusion
of stdio.h header file, which comes along with your compiler.
Including a header file is equal to copying the content of the header file but we
do not do it because it will be error-prone and it is not a good idea to copy the
content of a header file in the source files, especially if we have multiple source
files in a program.
A simple practice in C or C++ programs is that we keep all the constants,
macros, system wide global variables, and function prototypes in the header
files and include that header file wherever it is required.
Include Operation
The #include directive works by directing the C preprocessor to scan the
specified file as input before continuing with the rest of the current source file.
The output from the preprocessor contains the output already generated,
followed by the output resulting from the included file, followed by the output
that comes from the text after the #include directive.
For example, if you have a header file header.h as follows –
char *test (void);
and a main program called program.c that uses the header file, like this –
int x;
#include "header.h"
int main (void) {
puts (test ());
}
9. the compiler will see the same token stream as it would if program.c read.
int x;
char *test (void);
int main (void) {
puts (test ());
}
Once-Only Headers
If a header file happens to be included twice, the compiler will process its
contents twice and it will result in an error. The standard way to prevent this is
to enclose the entire real contents of the file in a conditional, like this −
#ifndef HEADER_FILE
#define HEADER_FILE
the entire header file file
#endif
This construct is commonly known as a wrapper #ifndef. When the header is
included again, the conditional will be false, because HEADER_FILE is
defined. The preprocessor will skip over the entire contents of the file, and the
compiler will not see it twice.
Computed Includes
Sometimes it is necessary to select one of the several different header files to
be included into your program. For instance, they might specify configuration
parameters to be used on different sorts of operating systems. You could do
this with a series of conditionals as follows −
#if SYSTEM_1
# include "system_1.h"
#elif SYSTEM_2
# include "system_2.h"
#elif SYSTEM_3
...
#endif