Preprocessor#
The previous chapter established that the preprocessor receives a stream of tokens from phase 3. Its output is also a stream of tokens, passed directly into phase 5.
The preprocessor is therefore purely a token manipulation tool. A source file with no preprocessor directives has a no-op phase 4: the token stream passes through unchanged.
What it can do falls into two categories:
Generate new tokens: inserting code the programmer didn’t write explicitly
Modify existing tokens: replace, remove, or transform them
What it cannot do is evaluate expressions: 1 + 3, sizeof(int),
strlen("Hello")[1] are all resolved later, during phase 7. The
preprocessor only sees tokens, never their values.
Interacting with the preprocessor is done by starting a line with the # character, followed by a preprocessing directive. (Any number of spaces can be present before and after the # character)
Directives#
File inclusion#
#include <filename>Look for a file called filename in folders provided to the preprocessor[2] with the
-Iflag, and in standard folders configured at compiler installation. Once the file is found, its content is pasted verbatim in place of the#includeline
#include "filename"Same as above, but look into the current directory first
Source : cppreference
Note
No assumption is made about the content of the included file, it technically doesn’t have to be valid C, or even code at all…
Macros#
Object-like#
#defineidentifier replacementAfter this directive, each occurrence of identifier in the source code is replaced by replacement.
#defineidentifierEquivalent to
#define identifier 1
Function-like#
#defineidentifier(parameters)replacementAfter this directive, each occurrence of identifier(values) in the source code is replaced by replacement, with each parameter name substituted by the corresponding value at the invocation site.
#defineidentifier(parameters, ...)replacementSimilar to the previous definition, but zero or more extra parameters can be supplied. The identifier
__VA_ARGS__will be replaced by those extra parameters. Additionally,__VA_OPT__(x)will be replaced by nothing if zero extra parameters were supplied, or byxif at least one extra parameter was supplied.
Source: cppreference
Conditional inclusion#
#ifcondition A#elseB#endifEvaluates condition (so at preprocessor-time), then replaces the whole
#if…#endifblock with A or B depending on the result.#ifdefMACROEquivalent to
#if defined(MACRO)#ifndefMACROEquivalent to
#if !defined(MACRO)#elifcondition2 B#endifAn alternative form for chaining multiple conditions without nesting, equivalent to:
#else # if condition2 B # endif #endif
#elifdefMACROAdded in C23 for consistency, equivalent to
#elif defined(MACRO)#elifndefMACROAdded in C23 for consistency, equivalent to
#elif !defined(MACRO)
Source: cppreference
The operators#
Both operators act directly on tokens: it’s the only unit the preprocessor works with.
#Set token type to string literal
name name
##Concatenate 2 tokens
some thing something
These operators can only be used on parameters of function-like macros.
Source: cppreference
Perspective#
The directives and operators above are a small set of low-level primitives: file inclusion, name substitution, token stringification, and token concatenation.
Yet because they operate before the language is parsed — on raw tokens, not on types, values, or scopes — they are unconstrained by what C itself allows. The preprocessor cannot change the language, but it can generate whatever C code is needed, making restrictions invisible at the source level.
With the full set of preprocessor tools catalogued, chapter 2 illustrates their use through existing macros; chapter 3 then applies them to construct a logging utility.