Advanced 13 min

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:

1 // myheader.h
2 #ifndef MYHEADER_H
3 #define MYHEADER_H
4
5 void myFunction();
6
7 #end

Quick Quiz

  1. When does the preprocessor run?
Before compilation
At runtime
After compilation
During compilation
  1. What is the modern way to prevent multiple inclusions of a header?
#include_once
#pragma once
Both work, but #pragma once is simpler
#ifndef guards
  1. Why should you prefer const over #define for constants?
const has type safety and scope
const uses less memory
#define cannot store numbers
#define is slower

Step Through the Code

Walk through the code step by step. Watch how variables change and see the program output at each line.

Lesson Progress

  • Fix This Code
  • Quick Quiz
  • Practice Playground - run once