The C preprocessor runs before the compiler sees your code. It performs text substitution, conditional compilation, and file inclusion. Understanding it prevents a class of bugs that are invisible to the compiler but break your program at runtime.
How the Preprocessor Works
The build pipeline is: source → preprocessor → compiler → linker → executable. The preprocessor output is pure C with all macros expanded and includes inlined — you can inspect it:
gcc -E program.c -o program.i /* saves preprocessor output */
#include — Insert File Contents
#include <stdio.h> /* system header — searched in standard paths */
#include "myheader.h" /* project header — searched relative to current file first */
#include literally copies the content of the named file into your source at that point. This is why header files use include guards — to prevent the same content being pasted twice when multiple files include the same header.
#define — Macros
Constants
#define PI 3.14159265358979
#define MAX_SIZE 1024
#define APP_NAME "Online C Compiler"
int arr[MAX_SIZE]; /* preprocessed to: int arr[1024]; */
The preprocessor replaces every occurrence of PI with 3.14159265358979 before compilation. There is no type, no scope, no debugger symbol. Prefer const variables for typed constants:
const int MAX_SIZE = 1024; /* has type, scope, and debugger visibility */
Function-like macros
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
The extra parentheses are essential. Without them:
#define BAD_SQUARE(x) x * x
BAD_SQUARE(3 + 1) /* becomes: 3 + 1 * 3 + 1 = 7, not 16 */
#define GOOD_SQUARE(x) ((x) * (x))
GOOD_SQUARE(3 + 1) /* becomes: ((3+1) * (3+1)) = 16 */
Always parenthesize both the entire macro and every parameter.
The double-evaluation problem
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int x = 5;
int result = MAX(x++, 3);
/* expands to: ((x++) > (3) ? (x++) : (3)) */
/* x is incremented TWICE if x > 3 */
Function-like macros evaluate their arguments multiple times. Use inline functions instead when arguments might have side effects:
static inline int max(int a, int b) { return a > b ? a : b; }
#ifdef, #ifndef, #if — Conditional Compilation
#include <stdio.h>
#define DEBUG 1
int main() {
int result = 42;
#ifdef DEBUG
printf("Debug: result = %dn", result);
#endif
printf("Result: %dn", result);
return 0;
}
Remove #define DEBUG (or compile with -UDEBUG) and the debug printf disappears completely — zero runtime cost. Enable it with -DDEBUG on the compiler command line:
gcc -DDEBUG -o program program.c /* defines DEBUG without editing source */
Use this pattern rather than commenting out debug code — it is cleaner and compilers can still type-check the disabled code.
Include Guards — Prevent Double Inclusion
/* myheader.h */
#ifndef MYHEADER_H
#define MYHEADER_H
typedef struct {
int x;
int y;
} Point;
void move(Point *p, int dx, int dy);
#endif /* MYHEADER_H */
The first time myheader.h is included, MYHEADER_H is not defined, so everything between #ifndef and #endif is processed and MYHEADER_H is defined. The second time it is included, MYHEADER_H is already defined, so the entire content is skipped.
Modern compilers support #pragma once as a simpler alternative:
#pragma once /* same effect as include guards — non-standard but universally supported */
#pragma — Compiler Hints
#pragma once /* prevent double inclusion */
#pragma GCC optimize("O3") /* request O3 for this file */
#pragma pack(1) /* pack structs with no padding */
Pragmas are implementation-defined. Unknown pragmas are ignored. Use them sparingly — they reduce portability. Prefer compiler flags over #pragma GCC extensions where possible.
Predefined Macros
#include <stdio.h>
int main() {
printf("File: %sn", __FILE__);
printf("Line: %dn", __LINE__);
printf("Function: %sn", __func__);
printf("Date: %sn", __DATE__);
return 0;
}
These are useful for debug logging and assertions. __LINE__ and __FILE__ are expanded by the preprocessor at each use, so they correctly report the file and line where they appear.
TL;DR
#include <>for system headers,#include ""for your own- Every header file needs include guards or
#pragma once - Always parenthesize macro arguments and the whole macro expression
- Avoid function-like macros for anything with side effects — use
inlinefunctions - Use
-DFLAGon the command line to define macros without editing source - Use
constvariables instead of#definefor typed constants - Test any macro or directive behaviour in our c online compiler — paste and run in seconds