The Preprocessor and Macros
#include, #define, include guards, and #if.
7 minute read
Big idea
Before the compiler sees your code, the preprocessor edits it as text. Every line starting with # is an instruction to it: paste in a file, replace a name, or keep or drop some lines.
The steps your code goes through
The preprocessor knows nothing about C — no types, no variables. It only moves text around. Keep that in mind and macros stop being mysterious.
#include: paste a file here
#include <stdio.h> // a system header: look in the compiler's folders
#include "list.h" // your own header: look next to this file first
#include literally pastes the whole file in place of the line. That's how printf's declaration reaches your code.
#define: named constants
#define MAX_STUDENTS 40
#define PI 3.14159
Every MAX_STUDENTS after this line is replaced with 40 before compiling. No =, no ; — it's text replacement, not a variable.
#include <stdio.h>
#define ROWS 3
#define COLS 4
int main(void) {
int grid[ROWS][COLS];
for (int r = 0; r < ROWS; r++) {
for (int c = 0; c < COLS; c++) {
grid[r][c] = r * COLS + c;
printf("%3d", grid[r][c]);
}
printf("\n");
}
return 0;
}output
0 1 2 3
4 5 6 7
8 9 10 11Run and edit it in the app.
Change COLS to 5 and every use updates at once.
Macros with parameters
A #define can take arguments, like a tiny function that's pasted in:
#define SQUARE(x) x * x
But it's text replacement, which bites:
#include <stdio.h>
#define SQUARE(x) x * x
int main(void) {
printf("%d\n", SQUARE(2 + 3));
return 0;
}output
25Run and edit it in the app.
SQUARE(2 + 3) becomes 2 + 3 * 2 + 3, which is 11. The fix: brackets around every parameter and the whole body — #define SQUARE(x) ((x) * (x)).
Even with brackets, a macro may use its argument twice: SQUARE(i++) increments i twice. If a function would do, use a function.
Watch it
Conditional compilation
#if, #ifdef, #ifndef, #else, and #endif keep or drop whole chunks of code before compiling:
#include <stdio.h>
#define DEBUG 1
int main(void) {
int total = 0;
for (int i = 1; i <= 3; i++) {
total += i;
#if DEBUG
printf("[debug] i=%d total=%d\n", i, total);
#endif
}
printf("total = %d\n", total);
return 0;
}output
[debug] i=1 total=1
[debug] i=2 total=3
[debug] i=3 total=6
total = 6Run and edit it in the app.
Set DEBUG to 0 and the debug lines aren't just skipped — they're not in the program at all. The same trick picks code per platform: #ifdef _WIN32 … #else … #endif.
Include guards
If a header is included twice, everything in it is defined twice — an error. Every header you write should start and end like this:
#ifndef POINT_H
#define POINT_H
typedef struct { int x, y; } Point;
int distance2(Point a, Point b);
#endif
The first time, POINT_H isn't defined, so the body is kept and POINT_H gets defined. The second time, #ifndef POINT_H is false and the whole file is skipped. (Many compilers also accept a one-line #pragma once.)
Built-in macros
The preprocessor fills in a few names for you — great for error messages:
#include <stdio.h>
int main(void) {
printf("line %d\n", __LINE__);
printf("function %s\n", __func__);
return 0;
}output
line 4
function mainRun and edit it in the app.
__FILE__ gives the file name and __LINE__ the line number. (__func__ is technically the compiler's, not the preprocessor's, but it's used the same way.)
#define DOUBLE(x) x + x — what does 3 * DOUBLE(4) evaluate to?
Common mistake
Putting ; or = in a #define:
#define SIZE = 10; // ✗ SIZE becomes the text "= 10;"
int a[SIZE]; // → int a[= 10;]; a baffling error
#define SIZE 10 // ✓
Think of it like
Find-and-replace in a text editor, run on your file just before it's handed in. It doesn't understand the essay — it just swaps words exactly as told.
Key terms
- Preprocessor — Edits source text before compiling; handles
#lines. #include— Pastes in a file.- Macro (
#define) — A name (optionally with parameters) replaced by text. - Conditional compilation —
#if/#ifdef/#endifkeeping or dropping code. - Include guard —
#ifndef X / #define X / … / #endifso a header is used only once.
Check yourself
When does the preprocessor run?
With #define TRIPLE(x) x * 3 , what is TRIPLE(1 + 1)?
What are include guards for?