Preprocessor

Overview

The C preprocessor parses C source files (.h/ .c) for preprocessing directives and tokens to perform textual substitution on. The resulting text is stored in an ASCII intermediate file (.i).

Preprocessing directives are specified by placing a # character as the first non-whitespace character on a line.

Conditional Inclusion

The #if and #elif directives (closed with #endif) can be used to include source code depending on whether or not the corresponding controlling expression evaluates truthily.

In a preprocessor conditional, unknown identifiers evaluate to 0. To check whether or not an identifier has been defined, use defined. There also exist a number of equivalent formulations:

Source File Inclusion

The #include directive searches in an inplementation-defined sequence of locations for a header uniquely identified by the string found between either angle brackets (<...>) or quotes ("...").

Starting in C23, the __has_include operator can be used to check if a header is available for inclusion.

Binary File Inclusion

Beginning in C23, the #embed directive searches in an inplementation-defined sequence of locations for a resource uniquely identified by the string found between either angle brackets (<...>) or quotes ("...").

The directive is replaced with a comma-delimited list of integer constant expressions. As such, it is often used in the following way:

static char const example[] = {
#embed "resource.ext"
}

The __has_embed operator can be used to check if a resource is available for inclusion. In addition, there exist a few standard embed parameters that can control how embedding works:

Pragmas

A pragma directive refers, in most cases, to implementation-specific directives. The only exception is when the STDC token immediately follows the #pragma token.

Macros

Macros refer to #define directives that specify terms textually replaced by the preprocessor during compilation. For types that don't have literals that describe their constants, compound literals can be used on the replacement side of the macro:

#define NAME (T){ INIT }

Some commonly used macros recognized by the preprocessor include:

The preprocessor temporarily disables the definition of macros on expansion to avoid infinite recursion. To avoid any ambiguity in the resulting expression, parameter references should be surrounded by parentheses in the replacement text.

Implementation Flags

A few macros are defined by the implementation to indicate some subset of the language is unavailable:

Function-Like Macros

If the name of a macro is immediately followed by a left parenthesis, the macro is said to be function-like or functional.

Argument Checking

Arguments found in the replacement side of a functional macro should be protected by specifying with surrounding parentheses. Additionally, types of arguments can be enforced with a variety of different tricks:

Token Operators

The # operator is used to stringify an argument. For example, calling foo(bar) in the following yields "bar".

#define foo(T) #T

The ## operator is used to concatenate tokens. For example, calling foo(bar) in the following yields bar_init.

#define foo(T) T ## _init

Variadic Macros

The final parameter of a function-like macro may be .... This parameter corresponds to a sequence of arguments accessible inside macro expansion __VA_ARGS__.

Starting in C23, the macro __VA_OPT__ is introduced. This macro takes in a preprocessing token sequence. If __VA_ARGS__ is not empty, the token sequence is included. For example,

#define F(X, ...) fprintf("" X "" __VA_OPT__(,) __VA_ARGS__)
Powered by Forestry.md