The Preprocessor
The preprocessor is like reviewing blueprints before construction — before actual compilation begins, it expands macros, inserts header files, and prunes conditional branches, then hands the result to the compiler.
Preprocessing Pipeline
The C compilation process: source file → preprocessing → compilation → assembly → linking. The preprocessor runs before compilation, handling all directives that start with #.
You can use the -E flag to see only the preprocessed output:
gcc -E hello.c -o hello.i
#include Header Files
#include inserts the contents of the specified file verbatim at the current position.
Two Forms
#include <stdio.h>
#include "myheader.h"
< >searches in system directories" "searches the current directory first, then system directories
" " for your own headers and < > for standard library headers — this is the convention.
Common Standard Headers
| Header | Main Content |
|---|---|
stdio.h |
Input/output functions |
stdlib.h |
Memory allocation, type conversion, random numbers |
string.h |
String operations |
math.h |
Math functions |
ctype.h |
Character classification |
assert.h |
Assert macro |
#define Macro Definitions
Object-Like Macros
Define named constants:
#define PI 3.14159265
#define MAX_SIZE 100
#define AUTHOR "WebTutorial"
double area = PI * 10 * 10;
int arr[MAX_SIZE];
printf("Author: %s\n", AUTHOR);
Function-Like Macros
Macros with parameters look like functions but are text substitutions:
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int s = SQUARE(5);
int m = MAX(10, 20);
25
20
Macro Pitfalls and Parentheses
Pitfall 1: Missing Parentheses Cause Precedence Errors
#define DOUBLE(x) x + x
int result = DOUBLE(3) * 2;
After expansion this becomes 3 + 3 * 2, which yields 9 instead of the expected 12. Correct version:
#define DOUBLE(x) ((x) + (x))
Pitfall 2: Parameters with Side Effects
#define SQUARE(x) ((x) * (x))
int n = 3;
int s = SQUARE(n++);
After expansion this becomes ((n++) * (n++)), incrementing n twice — undefined behavior.
i++, func()) as macro arguments. Use inline functions or real functions instead.
Pitfall 3: Macros in if-else Branches
#define CHECK(cond) if (cond) printf("yes\n")
if (flag)
CHECK(x);
else
do_something();
After expansion, the else matches the macro's inner if, not the outer one — the classic dangling else problem. Wrap with do { ... } while(0):
#define CHECK(cond) do { if (cond) printf("yes\n"); } while(0)
Example
#include <stdio.h>
#define SAFE_MAX(a, b) ((a) > (b) ? (a) : (b))
#define SAFE_SQUARE(x) ((x) * (x))
#define SWAP(type, a, b) do { type _tmp = (a); (a) = (b); (b) = _tmp; } while(0)
int main(void) {
int x = 10, y = 20;
printf("max: %d\n", SAFE_MAX(x, y));
printf("square: %d\n", SAFE_SQUARE(5));
SWAP(int, x, y);
printf("after swap: x=%d y=%d\n", x, y);
double a = 3.5, b = 2.1;
printf("double max: %.1f\n", SAFE_MAX(a, b));
return 0;
}
max: 20
square: 25
after swap: x=20 y=10
double max: 3.5
Conditional Compilation
Conditional compilation lets the same source code produce different compiled output under different conditions. It's widely used for cross-platform support, debug switches, and feature trimming.
#if / #elif / #else / #endif
#define PLATFORM 2
#if PLATFORM == 1
const char *os = "Windows";
#elif PLATFORM == 2
const char *os = "Linux";
#else
const char *os = "Unknown";
#endif
printf("Platform: %s\n", os);
Platform: Linux
#ifdef / #ifndef
#ifdef checks whether a macro is defined; #ifndef checks whether it is not defined.
#define DEBUG
#ifdef DEBUG
printf("Debug mode: x=%d\n", x);
#endif
#ifndef BUFFER_SIZE
#define BUFFER_SIZE 256
#endif
#ifdef DEBUG is equivalent to #if defined(DEBUG), and #ifndef is equivalent to #if !defined(...).
Debug Switch
#ifdef DEBUG
#define LOG(msg) printf("[DEBUG] %s:%d %s\n", __FILE__, __LINE__, msg)
#else
#define LOG(msg)
#endif
LOG("Variable initialized");
Compile with -DDEBUG to enable logging; without it, the program stays silent:
gcc -DDEBUG -o myapp myapp.c
Example
#include <stdio.h>
#define LEVEL 3
int main(void) {
#if LEVEL >= 3
printf("Advanced features enabled\n");
#elif LEVEL >= 2
printf("Intermediate features enabled\n");
#else
printf("Basic features\n");
#endif
#ifdef VERBOSE
printf("Verbose output mode\n");
#else
printf("Concise output mode\n");
#endif
return 0;
}
Advanced features enabled
Concise output mode
Predefined Macros
The C standard predefines several useful macros:
| Macro | Meaning |
|---|---|
__FILE__ |
Current source file name |
__LINE__ |
Current line number |
__DATE__ |
Compilation date |
__TIME__ |
Compilation time |
__func__ |
Current function name (C99) |
__STDC__ |
Whether conforming to ANSI C standard |
printf("File: %s\n", __FILE__);
printf("Line: %d\n", __LINE__);
printf("Date: %s\n", __DATE__);
printf("Time: %s\n", __TIME__);
printf("Function: %s\n", __func__);
File: main.c
Line: 5
Date: Jun 25 2026
Time: 14:30:00
Function: main
#undef to Cancel a Macro
#undef cancels a previous macro definition:
#define MAX_SIZE 100
printf("%d\n", MAX_SIZE);
#undef MAX_SIZE
After #undef, MAX_SIZE is no longer a macro and can be redefined or used as a normal identifier.
❓ FAQ
#if defined is more flexible — you can combine multiple conditions: #if defined(A) && defined(B). #ifdef can only check a single macro.📖 Summary
#includeinserts header file contents into the source;< >searches system directories," "searches the current directory#definedefines object-like macros (constant substitution) and function-like macros (parameterized text substitution)- Macro parameters must be parenthesized, and the entire macro expression should be parenthesized too, to avoid precedence traps
- Never pass expressions with side effects as macro arguments
- Conditional compilation with
#if/#ifdef/#ifndefenables cross-platform support, debug switches, and feature trimming - Predefined macros like
__FILE__,__LINE__,__DATE__are useful for logging and debugging
📝 Exercises
- Define two versions of a SQUARE macro — one with parentheses and one without. Test both with
SQUARE(2+3)and observe the result difference - Use
#ifdef DEBUGto implement a logging macroLOG(fmt, ...)that outputs the file name and line number in DEBUG mode and produces no output otherwise - Use conditional compilation to implement cross-platform printing: print "Windows platform" on Windows, "Linux platform" on Linux, using
#if defined(_WIN32)to detect the platform



