Preprocessor Directives
Master preprocessor directives for conditional compilation and code generation
Learn how the C++ preprocessor works and how to use directives for conditional compilation, macros, and build configuration.
A Simple Example
#include <iostream>
#define DEBUG_MODE 1
#define MAX_SIZE 100
#define SQUARE(x) ((x) * (x))
#ifdef DEBUG_MODE
#define LOG(msg) std::cout << "[DEBUG] " << msg << "\n"
#else
#define LOG(msg)
#endif
int main() {
LOG("Program started");
int size{MAX_SIZE};
std::cout << "Max size: " << size << "\n";
int value{5};
std::cout << "Square of " << value << ": " << SQUARE(value) << "\n";
#if DEBUG_MODE
std::cout << "Debug information...\n";
#endif
return 0;
}
Breaking It Down
#define - Define Constants and Macros
- What it does: Text replacement before compilation
- Constants: #define MAX_SIZE 100 replaces all MAX_SIZE with 100
- Macros: #define SQUARE(x) ((x) * (x)) creates function-like macro
- Remember: No semicolon at the end of #define
#ifdef, #ifndef, #if - Conditional Compilation
- #ifdef NAME: Compiles code only if NAME is defined
- #ifndef NAME: Compiles code only if NAME is NOT defined
- #if CONDITION: Compiles code if CONDITION is true
- Remember: Use #endif to close conditional blocks
Header Guards with #pragma once
- Problem: Header files can be included multiple times
- Modern solution: #pragma once at top of header
- Old solution: #ifndef HEADER_H / #define HEADER_H / #endif
- Remember: #pragma once is simpler and widely supported
Macro Pitfalls and Modern Alternatives
- Macros have no type safety or scope
- Prefer const or constexpr for constants
- Prefer inline functions over function-like macros
- Remember: Use macros sparingly in modern C++
Why This Matters
- The preprocessor runs before compilation, manipulating source text. It enables platform-specific code, debug builds, feature flags, and header guards.
- Understanding the preprocessor is essential for managing builds across platforms and configurations.
- It powers critical features like header guards, conditional debug code, and cross-platform compatibility.
Critical Insight
The preprocessor is a text manipulation tool that runs BEFORE the compiler sees your code. It literally does find-and-replace on your source text. When you write #define MAX 100, every occurrence of MAX becomes 100 before compilation even starts.
This is powerful but dangerous - macros have no type checking, no scope, and can cause unexpected behavior. Modern C++ prefers const, constexpr, and inline functions, but the preprocessor remains essential for conditional compilation and platform-specific code.
Best Practices
Use #pragma once for header guards: Simpler and less error-prone than traditional #ifndef guards.
Prefer const/constexpr over #define: For constants, use const or constexpr which provide type safety and proper scoping.
Prefer inline functions over macros: Function-like macros lack type safety. Use inline functions or templates instead.
Parenthesize macro arguments: Always wrap macro parameters in parentheses to prevent operator precedence issues.
Common Mistakes
No semicolon in #define: #define MAX 100; creates problems. The semicolon becomes part of the replacement.
Macro side effects: SQUARE(x++) expands to ((x++) * (x++)) which increments x twice!
Missing parentheses in macros: #define SQUARE(x) x*x fails with SQUARE(2+3) becoming 2+3*2+3 instead of 25.
Forgetting #endif: Every #ifdef, #ifndef, or #if must be closed with #endif.
Debug Challenge
This header guard is incomplete. Click the highlighted line to fix it:
Quick Quiz
- When does the preprocessor run?
- What is the modern way to prevent multiple inclusions of a header?
- Why should you prefer const over #define for constants?
Step Through the Code
Walk through the code step by step. Watch how variables change and see the program output at each line.
Variables
Output
Stack / Heap
Output:
Error:
Lesson Progress
- Fix This Code
- Quick Quiz
- Practice Playground - run once