Skip to content
FollowtheDamnCode

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

From .c file to running program

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.

Constants with #define
#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 11

Run 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:

Fix the bug: SQUARE(2 + 3) should be 25
#include <stdio.h>

#define SQUARE(x) x * x

int main(void) {
    printf("%d\n", SQUARE(2 + 3));
    return 0;
}
output
25

Run 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:

Switch debug output on and off
#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 = 6

Run 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:

Where am I?
#include <stdio.h>

int main(void) {
    printf("line %d\n", __LINE__);
    printf("function %s\n", __func__);
    return 0;
}
output
line 4
function main

Run 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 / #endif keeping or dropping code.
  • Include guard — #ifndef X / #define X / … / #endif so 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?