© Mikael Olsson 2019
Mikael OlssonModern C Quick Syntax Referencehttps://doi.org/10.1007/978-1-4842-4288-9_18

18. Preprocessor

Mikael Olsson1 
(1)
Hammarland, Länsi-Suomi, Finland
 
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.
#define SQUARE(x) x*x
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.
#define DEBUG_LEVEL 3
#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).
#define DEBUG
#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.
#line 5 "myapp.c"

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__ "..."