Last 30 Days
No notifications
Before C ever compiles your code, a tool called the preprocessor runs over it and edits the source — pasting in headers, expanding macros, stripping comments, conditionally hiding code. Every line that starts with # is a preprocessor directive.
#include <stdio.h> /* paste contents of stdio.h here */
#define PI 3.14159f /* every PI later -> 3.14159f */
#define SQUARE(x) ((x)*(x)) /* function-like macro */int main(void) {
printf("%f\n", PI);
printf("%d\n", SQUARE(5)); /* expands to ((5)*(5)) */
return 0;
}
| Directive | Purpose |
#include | Paste another file in here |
#define | Replace a name with text |
#ifdef / #ifndef / #endif | Compile this block only sometimes |
#include "..." vs #include <...>#include <stdio.h> /* angle brackets — search SYSTEM directories */
#include "myutils.h" /* quotes — search MY project first, then system */#ifndef MYUTILS_H
#define MYUTILS_H
/* declarations */
#endifStops the same header being pasted twice into the same .c file.
The preprocessor is a text-substitution engine. It runs first, transforms your source, and hands the result to the actual compiler. Every # directive is a message to the preprocessor — not to the compiler.
You can see exactly what it produces with gcc -E file.c (try it!).
#include <stdio.h> /* system header — found via -I include path */
#include "config.h" /* user header — relative to current file first */The preprocessor literally drops the contents of that file at this exact line. That's why headers contain only declarations (int add(int, int);), not definitions — definitions would be repeated and cause "multiple definition" errors at link time.
If utils.h is included by both a.h and b.h, and your .c includes both, you'd paste utils.h twice — duplicate type definitions = error. Guards fix it:
/* utils.h */
#ifndef UTILS_H
#define UTILS_Hvoid greet(const char *name);
typedef struct { int x, y; } Point;
#endif /* UTILS_H */
Modern compilers also accept #pragma once (one line, less error-prone — but not in the C standard):
#pragma once#define PI 3.14159f
#define MAX_USERS 1024
#define APP_NAME "CampusCrate"Every occurrence of the name (outside a string) is replaced before compilation. Conventionally written in UPPER_SNAKE_CASE.
> Modern C prefers const or enum for constants — they have a type and respect scope:
>
>
> static const int MAX_USERS = 1024;
> enum { BUFFER_SIZE = 256 };
>#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))Wrap arguments AND the whole expansion in parentheses. Otherwise:
#define SQUARE(x) x * x
SQUARE(2 + 3) /* -> 2 + 3 * 2 + 3 = 11, not 25! */#define MAX(a, b) ((a) > (b) ? (a) : (b))
int x = MAX(i++, 5); /* i++ may execute TWICE — undefined */A real function evaluates each argument once. A macro pastes the text — so i++ appears twice in the expansion. For anything non-trivial, prefer an inline function.
Compile (or skip) blocks of code based on macros:
#define DEBUG 1#if DEBUG
printf("x = %d\n", x);
#endif
#ifdef _WIN32
/* Windows-specific code */
#elif defined(__linux__)
/* Linux-specific code */
#else
/* fallback */
#endif
#ifndef NDEBUG
assert(p != NULL);
#endif
Used heavily for cross-platform code, debug logging, and feature flags.
The compiler gives you a few for free:
| Macro | Value |
__FILE__ | Current source file name |
__LINE__ | Current line number |
__DATE__ | Compilation date |
__TIME__ | Compilation time |
__func__ | Current function name (C99) |
__STDC_VERSION__ | C standard in use |
Great for logging:
#define LOG(msg) fprintf(stderr, "[%s:%d %s] %s\n", \
__FILE__, __LINE__, __func__, msg)End each line (except the last) with \:
#define SWAP(a, b) do { \
int _tmp = (a); \
(a) = (b); \
(b) = _tmp; \
} while (0)The do { ... } while (0) trick lets the macro be used like a single statement, semicolon and all.
#define MAX 100
/* ... use MAX ... */
#undef MAX /* MAX is no longer defined */#pragma once /* alternative to header guards */
#pragma pack(1) /* tighten struct packing */#if !defined(__STDC__)
#error "C99 or later required"
#endif
#error aborts compilation with your message — useful to enforce assumptions.
Inside a macro, #x becomes the string literal of x, and a##b glues two tokens together:
#define STR(x) #x
#define MAKE_VAR(n) var_##nprintf("%s\n", STR(hello)); /* prints "hello" */
int MAKE_VAR(1) = 42; /* declares var_1 = 42 */
Powerful but easy to abuse.
| Want to | Use |
| Pull in a library | #include |
| Pull in your file | #include "myfile.h" |
| Define a constant | #define NAME value |
| Define a macro | #define F(x) ((x) + 1) |
| Conditional code | #ifdef / #ifndef / #endif |
| Header guard | #ifndef X_H ... #define X_H ... #endif |
| Cancel a macro | #undef NAME |
| Force compile error | #error "message" |
The preprocessor is old, weird, and incredibly useful — and explains a lot of the surprising behaviour you'll see in real C codebases.