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?
Practice Playground
Time to try out what you just learned! Play with the example code below, experiment by making changes and running the code to deepen your understanding.
Output:
Error:
Lesson Progress
- Fix This Code
- Quick Quiz
- Practice Playground - run once