The Preprocessor
Before compiling, the preprocessor rewrites your source code: it pastes in header files, replaces macros, and includes or removes code depending on conditions. Used well, it gives you constants, include guards and platform-specific code; used carelessly, it produces some of C's most confusing bugs.
Directives
| Directive | Does |
|---|---|
#include <file.h> | Paste a system/library header |
#include "file.h" | Paste your own header (searched in the current folder first) |
#define NAME value | Define a macro |
#undef NAME | Remove a macro |
#if, #ifdef, #ifndef, #elif, #else, #endif | Conditional compilation |
#error "message" | Stop compilation with a message |
#pragma … | Compiler-specific instructions |
Function-like macros
Macros can take parameters. They're pure text substitution, which leads to two classic pitfalls:
#include <stdio.h> #define SQUARE_BAD(x) x * x #define SQUARE(x) ((x) * (x)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) int main(void) { printf("%d\n", SQUARE_BAD(2 + 3)); // becomes 2 + 3 * 2 + 3 printf("%d\n", SQUARE(2 + 3)); // becomes ((2 + 3) * (2 + 3)) printf("%d\n", MAX(7, 12)); return 0; }
11 25 12
- Missing parentheses change the meaning with operator precedence. Parenthesise every parameter and the whole result.
- Repeated evaluation:
MAX(i++, j)may incrementitwice, because the argument is pasted in twice.
For anything that behaves like a function, write a real function — marked static inline if you want it to be cheap. Compilers optimise small functions just as well, and functions have types and no surprises.
#include <stdio.h> static inline int max_int(int a, int b) { return a > b ? a : b; } int main(void) { int i = 5; int biggest = max_int(i++, 3); // i is incremented exactly once printf("%d, i is now %d\n", biggest, i); return 0; }
5, i is now 6
Include guards
If a header is included twice in the same file (often indirectly), its definitions would be duplicated. An include guard makes the second inclusion empty:
#ifndef GEOMETRY_H #define GEOMETRY_H typedef struct { double x, y; } Point; double distance(Point a, Point b); #endif
Most compilers also support the shorter #pragma once at the top of a header; it isn't standard, but it's widely used. Lesson 52 puts headers to work.
Conditional compilation
Code can be included only in some builds — debug output, platform-specific code, optional features:
#include <stdio.h> #define DEBUG 1 #if DEBUG #define LOG(msg) fprintf(stderr, "[debug] %s:%d: %s\n", __FILE__, __LINE__, msg) #else #define LOG(msg) ((void) 0) #endif int main(void) { LOG("starting"); int total = 0; for (int i = 1; i <= 4; i++) total += i; LOG("loop finished"); printf("total = %d\n", total); return 0; }
[debug] main.c:12: starting [debug] main.c:15: loop finished total = 10
Instead of editing the file, you can define macros on the command line: gcc -DDEBUG=0 main.c. __FILE__ and __LINE__ are predefined macros holding the current file name and line number.
Platform detection
Compilers predefine macros that identify the system:
#include <stdio.h> int main(void) { #if defined(_WIN32) const char *os = "Windows"; #elif defined(__APPLE__) const char *os = "macOS"; #elif defined(__linux__) const char *os = "Linux"; #else const char *os = "something else"; #endif printf("Compiled for %s, C standard %ld\n", os, (long) __STDC_VERSION__); return 0; }
Compiled for macOS, C standard 201710
__STDC_VERSION__ is 201710L for C17 and 202311L for C23.
Stringising and pasting
Inside a macro, #x turns an argument into a string, and a ## b joins two tokens. A neat use is printing an expression together with its value:
#include <stdio.h> #define SHOW(expr) printf("%s = %d\n", #expr, (expr)) int main(void) { int width = 7, height = 6; SHOW(width * height); SHOW(width > height); return 0; }
width * height = 42 width > height = 1
Seeing the result
When a macro misbehaves, look at what the preprocessor produced: gcc -E main.c prints the source after preprocessing (lesson 4).
Try it yourself
Use an X-macro: one list that generates both an enum and its matching names, so they can never get out of step.
0 APPLE 1 BANANA 2 CHERRY 3 DATE