5.1.27 · D1C Programming

Foundations — Preprocessor directives — #define, #ifdef, #ifndef, #include guards

1,748 words8 min readBack to topic

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

source file = raw text

tokens: chunks of text

directive: a hash line

substitution: blind replace

defined: on off switch

conditional: keep or delete block

include: paste a whole file

redefinition pain

define macros

include guards

Preprocessor directives

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 plain ribbon of text characters, not yet a runnable program.
A "token" is…
one indivisible chunk of the text (a name, number, or symbol) that tools match whole, never in pieces.
A line is a "directive" when…
its first non-space token is #; it is an order to the preprocessor, not a C statement, so it takes no semicolon.
"Substitution" means…
blindly deleting a target token and pasting replacement text in its place, with zero understanding of meaning or precedence.
"Blind" substitution is why macros need…
parentheses around arguments and the whole body, to force the intended grouping.
A name being "defined" is…
a simple on/off switch the preprocessor remembers after a #define runs.
#ifdef vs #ifndef ask…
"is this switch ON?" vs "is this switch OFF?" — keeping the block accordingly.
"Conditional compilation" decides…
at compile time, before the program runs, whether to keep or delete an entire block of text.
#include "x.h" does…
paste the entire text of x.h right where the include line sat.
A "redefinition error" is caused by…
the same declaration (e.g. a struct) reaching the compiler twice in one translation unit — exactly what happens when a header is pasted twice.
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.