# The Preprocessor

> Source: https://learn-c.net/preprocessor/
> Part of Learn C, free to read.

The preprocessor performs text substitution before compilation. It knows nothing about C's syntax or types — it moves characters around.

```bash
gcc -E main.c | less        # see exactly what the compiler receives
```

That command is the debugging tool for anything macro-related, and it is worth reaching for early.

## #include

```c
#include <stdio.h>      /* system headers — searched in the system paths */
#include "myheader.h"   /* your headers — searched relative to the file first */
```

`#include` literally pastes the file's contents in place. Which is why every header needs a guard against being pasted twice:

```c myheader.h
#ifndef MYHEADER_H
#define MYHEADER_H

typedef struct { int x, y; } point_t;
void draw(const point_t *p);

#endif /* MYHEADER_H */
```

Without the guard, including it from two places gives you a duplicate definition error. `#pragma once` does the same in one line and is supported by every compiler you are likely to use, though the `#ifndef` form remains the strictly portable one.

## Object-like macros

```c
#define MAX_USERS 100
#define PI 3.14159265358979
#define APP_NAME "myapp"
```

For constants, prefer the language's own facilities:

```c
enum { MAX_USERS = 100 };          /* has a type, visible to the debugger */
static const double PI = 3.14159;  /* has a type, respects scope */
```

A `#define` has no type and no scope — it applies from that line to the end of the translation unit, including inside other headers. `enum` constants and `const` values are visible to your debugger and obey normal scoping rules.

## Function-like macros, and their traps

```c
#define SQUARE(x) x * x

int a = SQUARE(3);        /* 9 — fine */
int b = SQUARE(1 + 2);    /* 1 + 2 * 1 + 2 = 5, NOT 9 */
int c = 10 / SQUARE(2);   /* 10 / 2 * 2 = 10, NOT 2.5 */
```

Pure text substitution, so precedence breaks. Parenthesise everything:

```c
#define SQUARE(x) ((x) * (x))
```

That fixes precedence and leaves a second problem — **double evaluation**:

```c
int i = 5;
int d = SQUARE(i++);      /* ((i++) * (i++)) — undefined behaviour */
```

Any argument with a side effect is evaluated twice. The same applies to the classic `MAX`:

```c
#define MAX(a, b) ((a) > (b) ? (a) : (b))
MAX(next_value(), other());     /* calls one of them twice */
```

**The fix is usually to not write a macro.** A `static inline` function has types, evaluates each argument once, and is just as fast:

```c
static inline int max_int(int a, int b) { return a > b ? a : b; }
```

Reserve function-like macros for the things a function genuinely cannot do — capturing `__FILE__` and `__LINE__`, or generating code.

## Multi-statement macros

```c
#define SWAP(a, b) do { int _t = (a); (a) = (b); (b) = _t; } while (0)
```

The `do { } while (0)` wrapper is the idiom that makes a multi-statement macro behave like a single statement, so it works correctly after an `if` with no braces. Without it:

```c
#define BAD_SWAP(a,b) { int t=(a); (a)=(b); (b)=t; }
if (x) BAD_SWAP(p, q); else other();     /* syntax error — the ; ends the if */
```

## Where macros genuinely earn their place

```c
#define LOG(fmt, ...) \
    fprintf(stderr, "[%s:%d] " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)

LOG("user %s not found", id);
/* [main.c:42] user abc not found */
```

A function cannot capture the caller's file and line. That is the canonical legitimate use.

```c
#define ARRAY_LEN(a) (sizeof (a) / sizeof (a)[0])

int nums[10];
for (size_t i = 0; i < ARRAY_LEN(nums); i++) { }
```

Also legitimate — and note it only works on a real array. Pass a *pointer* and you get garbage, which is [failure mode 4](/review/failure-modes/). Recent compilers warn via `-Wsizeof-pointer-div`.

## Conditional compilation

```c
#ifdef DEBUG
    fprintf(stderr, "state: %d\n", state);
#endif

#if defined(__linux__)
    /* Linux */
#elif defined(__APPLE__)
    /* macOS */
#else
#   error "unsupported platform"
#endif
```

```c
#ifdef NDEBUG        /* defined in release builds; disables assert() */
```

`#error` is underused: it turns an unsupported configuration into a clear compile-time message rather than a mysterious failure later.

Keep conditional compilation shallow. Deeply nested `#ifdef` blocks produce code where no single reader knows which lines are live, and where a change compiles on your machine and breaks someone else's.

## Useful predefined macros

```c
__FILE__      /* current file name */
__LINE__      /* current line number */
__func__      /* enclosing function name (C99; not a macro, but used the same way) */
__DATE__ __TIME__
__STDC_VERSION__   /* 201710L for C17 */
```

## Exercise

```c
#include <stdio.h>

/* 1. Write a header guard for a header declaring a `vec2_t` struct.
   2. Write a CHECK(cond, msg) macro that, when cond is false, prints
      file and line plus msg to stderr and returns -1 from the caller.
      Use do/while(0) and parenthesise properly.
   3. Replace this broken macro with a static inline function:
        #define CUBE(x) x*x*x
   4. Run `gcc -E` on your file and read what the compiler actually sees. */

int main(void) { printf("start\n"); return 0; }
```

## Common questions

### `#define` or `const` for a constant?

`enum` for integer constants and `static const` for others. Both have types, obey scope and appear in the debugger; a `#define` has none of those properties and leaks into every file that includes the header.

### When is a function-like macro the right tool?

When a function genuinely cannot do the job: capturing `__FILE__` and `__LINE__`, generating declarations, or working across types before you have generics. Everything else should be a `static inline` function, which is as fast and does not double-evaluate its arguments.

### Why does my macro give the wrong answer inside an expression?

Almost certainly missing parentheses. The preprocessor substitutes text, so `#define SQUARE(x) x * x` expands `SQUARE(1+2)` to `1 + 2 * 1 + 2`. Wrap both the whole body and every parameter: `((x) * (x))`.
