C Mastery / The Preprocessor: Includes and Object-Like Macros
Part 1 — The Core Language

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

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

Undefined Behavior

produce a program with UB (e.g., an unparenthesized macro leading to unintended evaluation or signed overflow).

Portability

implementation-defined.

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

matters; use macros for preprocessor-visible constants (array sizes, compile configuration).

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).

References

Verification

verified.`

Progress

Concept checkboxes

Mastery levels

ConceptCurrent level (0–8)Target level
c.pp.include05
c.pp.object-macro05