C Preprocessor Directives
Code that runs before compilation! Learn #include, #define macros, and conditional compilation with #ifdef.
Track Your Progress
Sign in to save your learning progress
What You Will Learn
- ✓Include header files correctly
- ✓Create macros with #define
- ✓Use conditional compilation
- ✓Understand header guards
01What is the Preprocessor?
Definition
The C Preprocessor is a tool that processes your code before compilation. It handles directives that start with # - like including files, defining constants, and conditional compilation.
Compilation Process
Source Code
.c file
Preprocessor
#include, #define
Compiler
Translation
Executable
.exe / a.out
Key Preprocessor Directives
- •
#include- Include header files - •
#define- Define macros and constants - •
#ifdef / #ifndef / #endif- Conditional compilation - •
#pragma- Compiler-specific instructions
What's Really Happening?
The preprocessor is like a smart text editor that runs BEFORE the compiler sees your code:
#include: Literally copy-pastes the entire header file into your code. If you include 10 files, your code grows by 10 files worth!
#define: Find-and-replace. Every time it sees PI, it replaces with 3.14159. NO memory is used!
#ifdef: Includes or excludes entire blocks of code based on conditions. Different code for Windows vs Linux!
See it yourself: Run gcc -E file.c to see the preprocessed output!
02#include Directive
The #include directive tells the preprocessor to copy the contents of another file into your code. There are two forms:
<angle brackets>
#include <stdio.h>#include <stdlib.h>#include <string.h>Used for standard library headers. Searches system include directories.
"double quotes"
#include "myheader.h"#include "utils/helper.h"#include "../config.h"Used for your own headers. Searches current directory first.
Creating Your Own Header File
1// mymath.h - Header file (declarations)2#ifndef MYMATH_H // Header guard - prevents double inclusion3#define MYMATH_H45// Function declarations (prototypes)6int add(int a, int b);7int subtract(int a, int b);8int multiply(int a, int b);910#endif1// mymath.c - Implementation file (definitions)2#include "mymath.h"34int add(int a, int b) {5 return a + b;6}78int subtract(int a, int b) {9 return a - b;10}1112int multiply(int a, int b) {13 return a * b;14}1// main.c - Using the header2#include <stdio.h>3#include "mymath.h"45int main() {6 printf("5 + 3 = %d\n", add(5, 3));7 printf("5 - 3 = %d\n", subtract(5, 3));8 printf("5 * 3 = %d\n", multiply(5, 3));9 return 0;10}03#define Macros
The #define directive creates macros - text replacements done by the preprocessor before compilation.
Simple Constants
1#include <stdio.h>23// Define constants4#define PI 3.141595#define MAX_SIZE 1006#define GREETING "Hello, World!"78int main() {9 double area = PI * 5 * 5; // PI replaced with 3.1415910 int arr[MAX_SIZE]; // MAX_SIZE replaced with 10011 printf("%s\n", GREETING); // GREETING replaced with string12 return 0;13}Macros with Parameters
1#include <stdio.h>23// Macro functions (no function call overhead)4#define SQUARE(x) ((x) * (x))5#define MAX(a, b) ((a) > (b) ? (a) : (b))6#define MIN(a, b) ((a) < (b) ? (a) : (b))7#define ABS(x) ((x) < 0 ? -(x) : (x))89int main() {10 int n = 5;11 printf("Square of %d: %d\n", n, SQUARE(n)); // Output: 2512 printf("Max(3, 7): %d\n", MAX(3, 7)); // Output: 713 printf("Min(3, 7): %d\n", MIN(3, 7)); // Output: 314 printf("Abs(-10): %d\n", ABS(-10)); // Output: 1015 return 0;16}Macro Pitfall: Always Use Parentheses!
// BAD - No parentheses#define SQUARE_BAD(x) x * x// Problem: SQUARE_BAD(3+2) expands to 3+2 * 3+2 = 3+6+2 = 11 (WRONG!)// GOOD - With parentheses#define SQUARE_GOOD(x) ((x) * (x))// SQUARE_GOOD(3+2) expands to ((3+2) * (3+2)) = 5 * 5 = 25 (CORRECT!)#undef - Undefine a Macro
#define DEBUG 1// ... code that uses DEBUG ...#undef DEBUG // Now DEBUG is undefined// DEBUG no longer exists here04Conditional Compilation
Conditionally include or exclude code based on defined macros. Useful for debugging, platform-specific code, and feature flags.
#ifdef / #ifndef / #endif
1#include <stdio.h>23#define DEBUG // Comment this line to disable debug mode45int main() {6 int x = 10;7 8 #ifdef DEBUG9 printf("[DEBUG] x = %d\n", x); // Only compiled if DEBUG is defined10 #endif11 12 printf("Result: %d\n", x * 2);13 14 #ifndef RELEASE15 printf("[DEV] Development build\n"); // Only if RELEASE is NOT defined16 #endif17 18 return 0;19}#if / #elif / #else / #endif
1#include <stdio.h>23#define VERSION 245int main() {6 #if VERSION == 17 printf("Running Version 1\n");8 #elif VERSION == 29 printf("Running Version 2\n");10 #else11 printf("Unknown Version\n");12 #endif13 14 #if defined(DEBUG) && VERSION >= 215 printf("Debug mode for V2+\n");16 #endif17 18 return 0;19}Platform-Specific Code
1#include <stdio.h>23void clearScreen() {4 #ifdef _WIN325 system("cls"); // Windows6 #else7 system("clear"); // Linux/Mac8 #endif9}1011int main() {12 #if defined(_WIN32) || defined(_WIN64)13 printf("Running on Windows\n");14 #elif defined(__linux__)15 printf("Running on Linux\n");16 #elif defined(__APPLE__)17 printf("Running on macOS\n");18 #else19 printf("Unknown platform\n");20 #endif21 22 return 0;23}05Header Guards
Header guards prevent a header file from being included multiple times, which would cause "redefinition" errors.
Without Header Guard
// myheader.hstruct Point { int x, y;};// If included twice:// ERROR: redefinition of 'struct Point'✓With Header Guard
// myheader.h#ifndef MYHEADER_H#define MYHEADER_Hstruct Point { int x, y;};#endif // MYHEADER_HModern Alternative: #pragma once
// myheader.h#pragma once // Non-standard but widely supportedstruct Point { int x, y;};#pragma once is simpler but not part of the C standard. Traditional header guards are more portable.
!Code Pitfalls: Common Mistakes & What to Watch For
These are the most common mistakes that trip up beginners. Study them carefully to avoid hours of debugging!
Mistake 1: Semicolon after #define
// WRONG#define MAX 100; // Semicolon included in replacement!int arr[MAX]; // Becomes: int arr[100;]; - ERROR!// CORRECT#define MAX 100int arr[MAX]; // Becomes: int arr[100];Mistake 2: Space in macro name
// WRONG#define MAX VALUE 100 // 'MAX' is defined as 'VALUE 100'// CORRECT#define MAX_VALUE 100Mistake 3: Missing parentheses in macro
// WRONG#define DOUBLE(x) x + xint result = DOUBLE(5) * 2; // 5 + 5 * 2 = 5 + 10 = 15 (WRONG!)// CORRECT#define DOUBLE(x) ((x) + (x))int result = DOUBLE(5) * 2; // ((5) + (5)) * 2 = 10 * 2 = 20Mistake 4: Side effects in macro arguments
#define SQUARE(x) ((x) * (x))int a = 5;int result = SQUARE(a++); // Expands to: ((a++) * (a++))// a is incremented TWICE! Result is undefined behavior.// Solution: Use inline functions for complex operations08Frequently Asked Questions
Q:What are predefined macros I can use?
A: The compiler provides useful macros:__FILE__ (current filename),__LINE__ (line number),__DATE__ (compile date),__TIME__ (compile time),__func__ (current function name). These are invaluable for debugging and logging.
Q:When should I use #define vs const?
A: Use const for type-safe constants that respect scope. Use #define for conditional compilation, macro functions, and string constants. Modern C prefersconst and inline functions over macros because they're type-checked and debuggable.
Q:Why do I need header guards?
A: Without guards, if file A includes header.h and file B includes both A and header.h, the header contents are included twice — causing "redefinition" errors. #ifndef/#define/#endif ensures the header is only processed once per compilation unit.
Q:Why wrap macro arguments in parentheses?
A: Macros do text substitution, not evaluation.#define SQ(x) x*x with SQ(1+2) becomes1+2*1+2 = 5, not 9! With parentheses:#define SQ(x) ((x)*(x)) gives ((1+2)*(1+2)) = 9.
Q:What's the difference between <header.h> and "header.h"?
A: Angle brackets <> search system include directories (standard library headers). Quotes "" search the current directory first, then system directories. Use <> for standard headers, "" for your own headers.
Q:Can I see what the preprocessor outputs?
A: Yes! Use gcc -E file.c to output the preprocessed code without compiling. This shows all macro expansions and included files — invaluable for debugging macro issues.
Q:What is the difference between #include <file> and #include "file"?
A: Angle brackets (<file.h>) search system include paths first, used for standard library headers. Double quotes ("file.h") search the current directory first, then system paths — use for your own headers.
Q:How do I define macros from the command line?
A: Use the -D flag with gcc:gcc -DDEBUG -DVERSION=2 file.c. This is equivalent to having#define DEBUG and #define VERSION 2at the top of your file. Very useful for build configurations.
Q:What are __LINE__ and __FILE__ used for?
A: These predefined macros expand to the current line number and filename. They're invaluable for debugging and logging:printf("Error at %s:%d", __FILE__, __LINE__);tells you exactly where an error occurred.
08Summary
What You Learned:
- ✓#include: Include header files (use <> for standard, "" for your own)
- ✓#define: Create constants and macro functions
- ✓Conditional: #ifdef, #ifndef, #if, #elif, #else, #endif
- ✓Header Guards: Prevent multiple inclusion with #ifndef/#define/#endif
- ✓Best Practices: Always use parentheses in macros, no semicolons in #define
09Advanced Preprocessor Techniques
Stringification (#)
The # operator converts a macro argument to a string literal.#define PRINT_VAR(x) printf(#x " = %d\n", x)turns PRINT_VAR(count) intoprintf("count" " = %d\n", count). Useful for debugging macros.
Token Pasting (##)
The ## operator concatenates tokens.#define CONCAT(a, b) a##b makesCONCAT(my, Var) become myVar. This is powerful for generating function names or variable prefixes programmatically.
Variadic Macros
C99 introduced variadic macros with ... and __VA_ARGS__. Example: #define DEBUG(...) printf(__VA_ARGS__) accepts any number of arguments. This is commonly used for logging macros that wrap printf.
Detecting Compiler Features
Use predefined macros to detect the environment: __GNUC__ for GCC,_MSC_VER for MSVC, __STDC_VERSION__ for C standard version. This enables writing portable code that adapts to different compilers and standards.
10When to Use the Preprocessor
Good Uses vs Alternatives
Use the preprocessor for header guards, conditional compilation, and platform detection. For constants, prefer const or enum over #define when possible—they provide type checking. For inline code, use inline functions over macros for type safety.
Test Your Knowledge
Related Tutorials
Header Files in C
Deep dive into header files! Learn to create your own .h files, understand include guards, organize declarations, and follow best practices for modular code.
Multi-File Programs in C
Organize large projects into multiple files! Learn about header files, source files, the extern keyword, and how to compile multi-file programs.
C11 Standard Features
Master the C11 standard! Learn about _Generic, _Noreturn, static_assert, anonymous structs/unions, aligned_alloc, threads, and atomics introduced in C11.
Have Feedback?
Found something missing or have ideas to improve this tutorial? Let us know on GitHub!