Foundations — Preprocessor directives — #define, #ifdef, #ifndef, #include guards
This page assumes you know nothing. We collect every symbol, word, and squiggle the parent note leans on, define each in plain words, tie it to a picture, and say why the topic can't live without it. Read top to bottom — each block uses only things defined above it.
0. What is "source code" and a "file"?
The picture: imagine a scroll of characters, left to right, top to bottom. Nothing on that scroll "runs" yet. Tools will read and transform it.
Why the topic needs it: the preprocessor's whole job is editing this text. If you don't picture the file as raw text, "text substitution" has no meaning.
1. What is a "token"?
Look at the figure: the same characters PI*r*r are sliced into the tokens PI, *, r, *, r. The preprocessor replaces whole tokens, never pieces of a token — this is exactly why #define PI 3.14 never touches the PI inside a word like PIPE.
Why the topic needs it: #define NAME replacement works by matching the token NAME. Understanding "token" explains what does and doesn't get replaced.
2. The # symbol and "directive"
The picture: think of two coloured highlighters running over the scroll. Every # line gets a bright marker meaning "helper, read me and then erase me." When the helper is done, no # lines remain.
Why the topic needs it: every single thing on the parent page (#define, #ifdef, #include) is a directive. This is the ground floor.
3. "Substitution" — find-and-replace
The figure shows the scroll before and after: every token PI is swapped for the tokens 3.14159. The helper does not know PI is "a number" or "a constant" — it only knows "match this token, paste that text."
Why the topic needs it: #define is substitution. Every macro trap on the parent page is a substitution surprise.
4. "Defined" vs "not defined" — a yes/no switch
The picture: a row of light switches, one per name. #define DEBUG flips DEBUG ON. That switch has no value attached in the on/off sense — #define FOO with nothing after it still counts as ON.
Why the topic needs it: #ifdef asks "is this switch ON?"; #ifndef asks "is this switch OFF?". Without the switch picture, the conditionals are meaningless.
5. "Conditional" — keep or delete a block
Follow the figure top to bottom: the #ifdef DEBUG gate checks the DEBUG switch. Switch ON → the enclosed lines survive to the compiler (green path). Switch OFF → those exact lines are cut out and thrown away (red path). This is a compile-time decision, made before the program ever runs — see Conditional compilation for cross-platform code for the OS-selection use.
Why the topic needs it: debug toggles, default values, and — crucially — include guards are all "keep-or-delete a block."
6. "Include" and "pasting a file"
The picture: the helper cuts open the scroll at the #include line and glues in the full contents of file.h, then continues. If two different #include lines point at the same file, its text gets glued in twice — the exact problem include guards fix. The unit of text you get after all includes and substitutions finish is called a translation unit; see Header files and translation units.
Why the topic needs it: without "pasting a whole file," the phrase "pasted twice → redefinition error" has nothing to describe, and include guards solve nothing.
7. "Redefinition" — why pasting twice hurts
The picture: you glue the same page into your notebook twice; now there are two copies of "the definition of Point," and the compiler complains it can't tell which one is real.
Why the topic needs it: this is the pain that include guards (Section 5 + Section 4 combined) exist to prevent. The guard uses a switch (Section 4) to keep the block (Section 5) only the first time it is pasted (Section 6).
How the foundations feed the topic
Read it as: raw text is chopped into tokens; # lines are directives; directives do substitution, flip switches, and paste files; switches + block-keeping give conditionals; conditionals + pasting + the redefinition pain give include guards. All roads meet at the parent topic.
Where this connects onward
- The whole pipeline (preprocess → compile → assemble → link) lives in Compilation pipeline — preprocess, compile, assemble, link.
- Whether to use a macro or a real function is decided in Macros vs inline functions.
- Switching a name ON from outside the file is done with Build flags -D and the command line.
Equipment checklist
You are ready for the parent topic if you can answer each of these out loud first, then reveal:
A .c file, before any tool runs, is best pictured as…
A "token" is…
A line is a "directive" when…
#; it is an order to the preprocessor, not a C statement, so it takes no semicolon."Substitution" means…
"Blind" substitution is why macros need…
A name being "defined" is…
#define runs.#ifdef vs #ifndef ask…
"Conditional compilation" decides…
#include "x.h" does…
A "redefinition error" is caused by…
Recall Feynman: the whole page in one breath
A source file is just text. Tools cut it into little chunks (tokens). Lines that start with # are notes to a helper (the preprocessor). The helper can replace a chunk with other text (#define), remember on/off switches (defined), keep or throw away whole blocks based on those switches (#ifdef/#ifndef), and glue in whole files (#include). Gluing the same file twice creates duplicate definitions, which is illegal — so we use a switch to keep a header's block only the first time. That's every idea the parent topic will use.