The preprocessor is a text substitution tool that modifies the source code before it is compiled. This modification is done according to the preprocessor directives that are included in the source files. The directives are easily distinguished from normal programming code in that they all start with a hash sign (
#). They must always appear as the first non-whitespace character on a line and do not need to end with a semicolon. The following table shows the preprocessor directives available in C along with their functions.
Directive | Description |
|---|
#include
| File include |
#define
#undef
| Define macro Undefine macro |
#ifdef
#ifndef
| If macro defined If macro not defined |
#if
#elif
#else
#endif
| If Else if Else End if |
#line
#error
#pragma
| Set line number Abort compilation Set compiler option |
Including Source Files
The
#include
directive inserts the contents of a file into the current source file. Its most common use is to include header files (
.h), both user-defined and library ones. Library header files are enclosed between angle brackets (
<>). This tells the preprocessor to search for the header in the default directory where it is configured to look for standard header files.
#include <stdio.h> /* search library directory */
Header files that you create for your own program are enclosed within double quotes (
""). The preprocessor will then search for the file in the same directory as the current file. In case the header is not found there, the preprocessor will then search among the standard header files.
#include "myfile.h" /* search current, then default */
The double quoted form can also be used to specify an absolute or relative path to the file.
#include "c:\myfile.h" /* absolute path */
#include "..\myfile.h" /* relative path */
Define
Another important directive is
#define
, which is used to create compile-time constants, also called
macros. After the directive, the name of the constant is specified followed by what it will be replaced by.
#define PI 3.14 /* macro definition */
The preprocessor will go through the code and change any occurrences of this constant with whatever comes after it in its definition until the end of the line.
double d = PI; /* d = 3.14 */
By convention, constants should be named in uppercase letters with each word separated by an underscore. That way, they are easy to spot when reading the source code.
Undefine
A
#define directive
should not be used to directly override a previously defined macro. Doing so will give a compiler warning, unless the macro definitions are the same. In order to redefine an existing macro, it first needs to be undefined using the
#undef
directive. Attempting to undefine a macro that is not currently defined will not generate a warning.
#undef PI /* undefine */
#undef PI /* allowed */
Predefined Macros
There are a number of macros that are predefined by the compiler. To distinguish them from other macros, their names begin and end with two underscores. The standard macros that all ANSI C-compliant
compilers include are listed in the following table.
Directive | Description |
|---|
__FILE__
| The name and path of the current file. |
__LINE__
| The current line number. |
__DATE__
| The compilation date in MM DD YYYY format. |
__TIME__
| The compilation time in HH:MM:SS format. |
__func__
| The name of the current function. Added in C99. |
__STDC__
| Defined as 1 if the compiler complies with the ANSI C standard. |
A common use for predefined macros is to provide debugging information. To give an example, the following error message includes the file name and line number where the message occurs.
printf("Error in %s at line %d", __FILE__, __LINE__);
Macro Functions
A macro can be made to take arguments. This allows them to define compile-time functions. For example, the following macro function gives the square of its argument.
#define SQUARE(x) ((x)*(x))
The macro function is called just as if it were a regular C function. Keep in mind that for this kind of function to work, the arguments must be known at compile time.
int x = SQUARE(2); /* 4 */
Note the extra parentheses in the macro definition that are used to avoid problems with operator precedence. Without the parentheses the following example would give an incorrect result, as the multiplication would then be carried out before the addition.
int main(void) {
int x = SQUARE(1+1); /* 1+1*1+1 = 3 */
}
To break a macro function across several lines, you can use the backslash character. This will escape the newline character that marks the end of a preprocessor directive. For this to work there must not be any whitespace after the backslash.
#define MAX(a,b) \
a>b ? \
a:b
Although macros can be powerful, they tend to make the code more difficult to read and debug. Macros should therefore only be used when they are necessary and should always be kept short. C code such as constant variables, enums, and inline functions can often accomplish the same goal more efficiently and safely than #define directives can.
Conditional Compilation
The directives used for
conditional compilation can include or exclude part of the source code if a certain condition is met. First, there are the
#if and
#endif directives, which specify a section of code that will be included only if the condition after the
#if directive is true. Note that this condition must evaluate to a constant expression.
#if DEBUG_LEVEL > 2
/* ... */
#endif
Just as with the C
if statement, any number of
#elif (else if) directives and one final
#else directive can be included.
#if DEBUG_LEVEL > 2
/* ... */
#elif DEBUG_LEVEL == 2
/* ... */
#else
/* ... */
#endif
Conditional compilation also provides a useful means of temporarily commenting out large blocks of code for testing purposes. This often cannot be done with the regular multi-line comment since they cannot be nested.
#if 0
/* Removed from compilation */
#endif
Compile if Defined
Sometimes, a section of code should only be compiled if a certain
macro has been defined, irrespective of its value. For this purpose, two special operators can be used:
defined and
!defined (not defined).
#if defined DEBUG
/* ... */
#elif !defined DEBUG
/* ... */
#endif
The same effect can also be achieved using the directives
#ifdef
and
#ifndef, respectively. The
#ifdef section is compiled only if the specified macro has been previously defined. Note that a macro is considered defined even if it has not been given a value.
#ifdef DEBUG
/* ... */
#endif
#ifndef DEBUG
/* ... */
#endif
Error and Warning
When the
#error directive is encountered, the compilation is aborted. This directive can be useful, for example, to determine whether or not a certain line of code is being compiled. It can optionally take a parameter that specifies the description of the generated compilation error.
#error "Compilation aborted"
Many C compilers also include the non-standard directive
#warning. This directive displays a warning message without halting the compilation.
#warning "Function X is deprecated, use Y instead"
Line
A less commonly used directive is
#line, which can change the line number that is displayed when an error occurs during compilation. Following this directive, the line number will as usual be increased by one for each successive line. The directive can take an optional string parameter that sets the file name that will be shown when an error occurs.
Pragma
The last standard directive is
#pragma, or pragmatic information. This directive is used to specify options to the compiler; and as such, they are vendor specific. To give an example,
#pragma message can be used with many compilers to output a string to the build window.
/* Show compiler message */
#pragma message "Compiling " __FILE__ "..."