Preprocessor & Macros
#, and produces a modified block of text that is what actually gets compiled. Understanding what it does — and its limits — explains a lot of C++'s stranger corners.#include, revisited
#include. It literally copies the contents of another file in, textually, at that exact point.include-forms.cpp
#include <iostream> // angle brackets: search system/standard library paths #include "myheader.h" // quotes: search relative to the current file first
#define — text substitution macros
#define creates a macro: a name that the preprocessor replaces with some replacement text, everywhere it appears, before compilation even starts.define-basics.cpp
#define PI 3.14159
#define MAX_USERS 100
double area(double radius) {
return PI * radius * radius; // becomes "3.14159 * radius * radius"
}The classic macro bug: missing parentheses
square-bug.cpp
#define SQUARE(x) x*x
int main() {
int result = SQUARE(1 + 2);
// The preprocessor expands this literally to: 1 + 2*1 + 2
// which evaluates to 1 + 2 + 2 = 5, NOT 9 as you'd expect!
return result;
}#define SQUARE(x) ((x)*(x)). That expands SQUARE(1 + 2) to ((1 + 2)*(1 + 2)), which correctly evaluates to 9. But even fixed, the macro still has no idea what type x is, won't respect namespaces or scoping, and will silently evaluate its argument twice — which matters if the argument has side effects. This is exactly why modern C++ steers away from macros for anything beyond simple constants.Header guards
#included more than once in the same translation unit (easy to happen indirectly, through other headers), its contents would be duplicated — leading to "redefinition" compiler errors. A header guard prevents this using conditional compilation.myheader.h
#ifndef MYHEADER_H
#define MYHEADER_H
struct Point {
double x;
double y;
};
#endif // MYHEADER_HMYHEADER_H is undefined, so the body runs and defines it. Any later include in the same file sees MYHEADER_H already defined and skips the body entirely, avoiding the duplicate definition.#pragma once
#pragma once, placed once at the top of a header, which achieves the same effect with a single line and no risk of a typo in the guard macro name colliding with another header. It isn't part of the official C++ standard, but it's supported everywhere in practice, and most codebases prefer it for its simplicity.myheader-pragma.h
#pragma once
struct Point {
double x;
double y;
};Conditional compilation
#ifdef, #ifndef, and #if let you compile different code depending on which macros are defined — commonly used for platform-specific code or debug-only logging.conditional-compilation.cpp
#include <iostream>
#define DEBUG_MODE 1
int main() {
#ifdef DEBUG_MODE
std::cout << "Debug build: extra logging enabled\n";
#endif
#if DEBUG_MODE == 1
std::cout << "Verbose diagnostics on\n";
#else
std::cout << "Release mode\n";
#endif
return 0;
}Why modern C++ avoids macros
Almost everything macros were historically used for now has a safer, type-checked replacement built into the language itself:
const/constexprfor named constants — typed, scoped, and debuggable, unlike#define PI 3.14159.inlinefunctions instead of function-like macros — arguments are evaluated once, types are checked, and you can step through them in a debugger.Templates for generic code that needs to work across types — macros cannot understand types at all.
Reserve
#definefor the cases language features cannot replace, like header guards or conditional compilation, and prefer#pragma oncefor the header-guard case.