Preprocessor directives — #define, #ifdef, #ifndef, #include guards
WHY does the preprocessor exist?
WHY we need it:
- Reuse code/text without re-typing →
#include. - Name magic constants so changes happen in one place →
#define. - Compile different code for different situations (debug vs release, Windows vs Linux) →
#ifdef/#ifndef. - Stop a header from being pasted twice → include guards.
The build pipeline:
#define — naming things by text substitution
HOW it works (object-like macro):
#define PI 3.14159
double area = PI * r * r; // becomes: 3.14159 * r * rWhy this step? The preprocessor literally swaps the text PI for 3.14159 before the compiler runs. By the time the compiler sees it, PI no longer exists.
Function-like macro (takes "arguments"):
#define SQUARE(x) ((x) * (x))
int y = SQUARE(3 + 1); // becomes: ((3 + 1) * (3 + 1)) = 16#ifdef, #ifndef, #endif — conditional compilation
WHY: sometimes you want code that exists only during debugging, or only on one OS. The preprocessor can delete entire chunks of code as text before the compiler sees them.
#define DEBUG
#ifdef DEBUG
printf("x = %d\n", x); // kept, because DEBUG is defined
#endif
#ifndef MAX
#define MAX 100 // define MAX only if nobody defined it yet
#endifWhy this step? #ifndef MAX guards against double-definition — only set MAX if it isn't already.
Include Guards — the killer use of #ifndef
/* file: types.h */
#ifndef TYPES_H /* if the unique tag is NOT defined... */
#define TYPES_H /* ...define it now (so next time we skip) */
struct Point { int x, y; }; /* the real header body */
#endif /* TYPES_H */HOW it works, step by step:
- First time the header is pasted:
TYPES_His undefined →#ifndefis true → preprocessor definesTYPES_Hand keeps the struct. - Second time it's pasted:
TYPES_His now defined →#ifndefis false → preprocessor deletes everything up to#endif. The struct appears only once. ✅
Worked examples
Common mistakes (steel-manned)
Flashcards
What stage runs before the C compiler and processes # lines?
Is a #define macro a typed variable?
Why wrap macro arguments in parentheses, e.g. ((x)*(x))?
What does #ifdef NAME do?
NAME is currently defined; deletes it otherwise.Difference between #ifdef and #ifndef?
#ifdef = compile if defined; #ifndef = compile if NOT defined.Write the include-guard pattern.
#ifndef TAG then #define TAG then body then #endif.What problem do include guards solve?
What's the non-standard one-line alternative to include guards?
#pragma once.Why is #define MAX 100; dangerous?
; is substituted too, breaking usages like arr[MAX] → arr[100;].What does #ifndef BUFFER_SIZE / #define BUFFER_SIZE 256 / #endif allow?
-DBUFFER_SIZE to override; otherwise a safe default of 256.Recall Feynman: explain it to a 12-year-old
Imagine you write an essay but leave little stickers like "PI" and "BIG_NUMBER". Before the teacher reads it, a helper goes through and replaces every sticker with the real words you told it to use. The helper can also follow notes like "only keep this paragraph if we're in test mode" and "if you already glued this page in once, don't glue it again." That helper is the preprocessor. It just copies, replaces, and removes text — it doesn't actually understand the essay. After the helper finishes, the real teacher (the compiler) reads the cleaned-up version.
Connections
- Header files and translation units
- const vs #define vs enum
- Compilation pipeline — preprocess, compile, assemble, link
- Macros vs inline functions
- Conditional compilation for cross-platform code
- Build flags -D and the command line
Concept Map
Hinglish (regional understanding)
Intuition Hinglish mein samjho
Dekho, C me jab tum code likhte ho, to compiler usse seedha nahi padhta. Pehle ek aur banda chalta hai jise preprocessor kehte hain. Ye banda bas text ka kaam karta hai — find-and-replace aur cut-paste. Wo C ko samajhta nahi, bas # se shuru hone wali lines ko follow karta hai. Isliye #define PI 3.14159 likhne ka matlab hai: "jahan jahan PI dikhe, wahan 3.14159 paste kar do" — compiler ke aane se pehle hi.
#define ka function-wala version, jaise #define SQUARE(x) ((x)*(x)), bahut kaam ka hai, par parentheses zaroori hain. Kyunki substitution blind hota hai, agar tum x*x likhoge to SQUARE(3+1) ban jaayega 3+1*3+1 = 7, jo galat hai. Har argument ko aur poore body ko () me lapeto, tabhi grouping sahi rahegi.
#ifdef aur #ifndef se tum conditional compilation kar sakte ho — yaani kuch code ko compile karna hai ya nahi, ye decide karna. #ifdef DEBUG ka matlab: agar DEBUG defined hai tabhi ye lines rakho, warna delete kar do. Iska sabse important use hai include guard: #ifndef TYPES_H → #define TYPES_H → ...body... → #endif. Isse ek header file agar do baar paste ho bhi jaaye, to body sirf ek baar aayegi, aur "redefinition" wala error nahi aayega. Modern shortcut hai #pragma once.
Yaad rakhna do cheezein: directive ke baad semicolon mat lagao (#define MAX 100; galat hai, kyunki ; bhi paste ho jaata hai), aur macro typed variable nahi hai — agar typed constant chahiye to const ya enum use karo. Bas itna samajh lo to preprocessor tumhara dost ban jaayega.