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
During compilation
After compilation
  1. What is the modern way to prevent multiple inclusions of a header?
#pragma once
#ifndef guards
Both work, but #pragma once is simpler
  1. Why should you prefer const over #define for constants?
const has type safety and scope
#define is slower
const uses less memory

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.

Lesson Progress

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