The Preprocessor: Includes and Object-Like Macros
This chapter introduces the C preprocessor: #include and object-like macros (#define). The preprocessor is a textual transformation phase that runs before compilation.
Why This Matters
The preprocessor is not C — it is a separate textual layer. Understanding that distinction, and the danger of naive macros, prevents a large class of hard-to-debug errors.
Prerequisites
c.core.1— translation units.
Core Concept
The preprocessing phase
The preprocessor runs before the compiler proper. It:
1. executes directives (#include, #define, #if, etc.); 2. expands macros; 3. removes comments.
The result is the translation unit the compiler actually sees.
#include
#include inserts the contents of another file:
#include <stdio.h> /* searches system include paths */
#include "myheader.h" /* searches the current directory first, then system */
The angle-bracket form searches implementation-defined system locations; the quote form searches a source-relative location first, then falls back to system locations.
Object-like macros
#define defines a macro that is textually replaced:
#define MAX_SIZE 1024
#define PI 3.14159
After definition, MAX_SIZE is replaced by 1024 wherever it appears (as a token), until #undef or end of file.
Syntax
#include <file>
#include "file"
#define NAME replacement
#undef NAME
Examples
A simple object-like macro
#include <stdio.h>
#define BUFFER_SIZE 256
int main(void)
{
char buf[BUFFER_SIZE];
printf("%zu\n", sizeof buf);
return 0;
}
Expected output: 256.
Multiple-line replacement
#define GREETING "hello, " \
"world"
A backslash at end of line continues the macro definition.
How It Works
Macro replacement is token-based, not string-based. The preprocessor scans tokens; when it finds a macro name, it substitutes the replacement tokens. Macros are not expanded inside string literals or character constants.
Variations
Predefined macros
The implementation provides predefined macros like __FILE__, __LINE__, __DATE__, __TIME__, and __STDC_VERSION__. These are used for diagnostics and feature detection (c.pp.conditional).
Function-like macros (next chapter)
#define SQUARE(x) ((x)*(x)) is a function-like macro. Its rules and pitfalls are covered in c.core.37.
Common Mistakes
- Thinking macros are type-checked (they are not; they are textual).
- Forgetting parentheses in replacement text (leading to precedence bugs).
- Defining a macro and expecting it to be scoped (macros ignore scope).
- Using
=or;in#defineincorrectly.
Undefined Behavior
- Macros themselves do not cause UB directly, but an incorrect expansion can
produce a program with UB (e.g., an unparenthesized macro leading to unintended evaluation or signed overflow).
Portability
- The preprocessor behavior is standard, but the exact include search paths are
implementation-defined.
- Predefined macros vary by implementation;
__STDC_VERSION__is the standard
way to detect the C version.
Under the Hood
The preprocessor produces a stream of tokens that the compiler parses. The compiler never sees the original macro names; it sees only the expanded tokens. This is why debuggers and error messages sometimes show expanded code or confusing line numbers.
Practical Usage
- Use
#includeguards to prevent double inclusion (c.pp.conditional). - Prefer
enumorconstover object-like macros for constants where type
matters; use macros for preprocessor-visible constants (array sizes, compile configuration).
- Parenthesize replacement text.
Exercises
1. Define a macro for a buffer size and use it to declare an array. 2. Show that a macro name inside a string literal is not expanded. 3. Print __FILE__ and __LINE__ from a function. 4. Demonstrate a precedence bug from an unparenthesized macro and fix it.
Deep Challenge
Explain why #define constants do not obey scope or type rules, and give two concrete scenarios where this causes bugs. For each, provide a safer alternative (const, enum, or a parenthesized macro).
Related Concepts
c.core.37— function-like macros.c.core.38— conditional compilation.c.build.1— the compilation pipeline.
References
- ISO/IEC 9899:2018 §6.10 (preprocessing directives).
Verification
- Preprocessing is token-based and precedes compilation.
VERIFIED - Macros are not expanded inside string literals.
VERIFIED - Include path search is implementation-defined.
VERIFIED - No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] #include (angle vs. quote)
- [ ] Object-like macros
- [ ] #undef
- [ ] Token-based replacement
- [ ] Predefined macros
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.pp.include | 0 | 5 |
| c.pp.object-macro | 0 | 5 |