Skip to content
  • Preprocessor & Macros · C++

Preprocessor & Macros

LearnViaNotes @learnvianotes· 2 min read

Before compilation, the preprocessor rewrites the source text: it pastes headers, expands macros and keeps or drops code with #if. It knows nothing about types or scopes.

Directives

DirectiveDoes
#include <x> / #include "x"Paste a header (system paths / project paths first)
#define NAME valueObject-like macro: textual replacement
#define F(a, b) (…)Function-like macro
#undef NAMERemove a macro
#if / #ifdef / #ifndef / #elif / #else / #endifConditional compilation
#pragma onceInclude guard (non-standard but universal)
#error msgStop compilation with a message

Macros and their traps

C++
#include <iostream>

#define SQUARE_BAD(x) x * x
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))

constexpr int square(int x) { return x * x; }       // the C++ way: typed, scoped, evaluated once

int main() {
    std::cout << SQUARE_BAD(2 + 3) << ' ' << SQUARE(2 + 3) << ' ' << square(2 + 3) << '\n';   // 2 + 3 * 2 + 3
    int i = 5;
    int m = MAX(i++, 0);                             // i++ is evaluated TWICE
    std::cout << m << ' ' << i << '\n';
}
Output
11 25 25
6 7

Where macros are still useful

C++
#include <iostream>

#define STRINGIFY(x) #x                         // # turns an argument into a string literal
#define CONCAT(a, b) a##b                       // ## pastes tokens
#define LOG_VAR(v) std::cout << #v << " = " << (v) << '\n'

#ifndef NDEBUG
#define DEBUG_ONLY(code) code
#else
#define DEBUG_ONLY(code)
#endif

int main() {
    int CONCAT(user, Count) = 3;                 // declares userCount
    LOG_VAR(userCount * 2);
    std::cout << STRINGIFY(hello world) << '\n';
    std::cout << __LINE__ << ' ' << (__cplusplus >= 202002L) << '\n';   // predefined macros
    DEBUG_ONLY(std::cout << "debug build\n";)
}
Output
userCount * 2 = 6
hello world
17 1
debug build
Predefined macroValue
__FILE__, __LINE__Current file name and line (C++20: prefer std::source_location)
__cplusplusStandard version, e.g. 202002L for C++20
NDEBUGDefined in release builds (-DNDEBUG) — disables assert
_WIN32, __linux__, __APPLE__Target platform
__GNUC__, __clang__, _MSC_VERCompiler

assert

C++
#include <cassert>
#include <iostream>
int divide(int a, int b) {
    assert(b != 0 && "divisor must be non-zero");   // checked only when NDEBUG is not defined
    return a / b;
}
int main() { std::cout << divide(10, 2) << '\n'; }
Output
5

Why prefer constexpr over #define for constants?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report