← Back to C++ Course | Chapter 16: Modern C++ | Lesson 9 of 9

C++ Preprocessor & Macros

The preprocessor is a helper that changes your code text before it is compiled, like an editor doing find-and-replace. Macros are shortcuts it expands for you.
Syntax
cpp
#define NAME value

#define MACRO(param) expression

#ifndef NAME
#define NAME
#endif

What is the Preprocessor?

The preprocessor is a distinct text-substitution pass that runs before actual compilation begins.

It scans the source file for any line beginning with a hash symbol (#) and processes those directives -- like #include, which literally inserts another file's text at that point -- before the compiler ever sees the resulting code.

Example: What is the Preprocessor?

cpp
// Include std::cout and std::cin
#include <iostream>

// Define the macro GREETING (replaced by the preprocessor)
#define GREETING "Hello"

// Program execution starts in main()
int main() {
	// Print to the console with cout
	std::cout << GREETING << std::endl;
	// Return 0 to signal that the program finished successfully
	return 0;
}

#define Constants

#define NAME value creates a macro that the preprocessor textually replaces with value everywhere NAME appears in the file afterward.

Because this substitution happens before compilation, the compiler never even sees the macro name -- it only sees the expanded result.

Example: #define Constants

cpp
// Include std::cout and std::cin
#include <iostream>

// Define the macro MAX_SCORE (replaced by the preprocessor)
#define MAX_SCORE 100

// Program execution starts in main()
int main() {
	// Print to the console with cout
	std::cout << MAX_SCORE << std::endl;
	// Return 0 to signal that the program finished successfully
	return 0;
}

Function-like Macros

Macros can also accept parameters, letting them behave superficially like functions -- #define SQUARE(x) ((x)*(x)).

But because this is pure text substitution with no type checking or scoping, macros can't be overloaded, debugged with normal tools, or trusted the way real functions can.

Example: Function-like Macros

cpp
// Include std::cout and std::cin
#include <iostream>

// Define the macro SQUARE (replaced by the preprocessor)
#define SQUARE(x) ((x) * (x))

// Program execution starts in main()
int main() {
	// Print to the console with cout
	std::cout << SQUARE(5) << std::endl;
	// Return 0 to signal that the program finished successfully
	return 0;
}

Conditional Directives

#ifdef, #ifndef, and #endif let you include or exclude entire blocks of code based on whether a macro has been defined, which is how cross-platform code compiles different implementations for Windows versus Linux, or how header guards prevent a header from being processed twice in one translation unit.

Example: Conditional Directives

cpp
// Include std::cout and std::cin
#include <iostream>

// Define the macro DEBUG_MODE (replaced by the preprocessor)
#define DEBUG_MODE

// Program execution starts in main()
int main() {
#ifdef DEBUG_MODE
	// Print to the console with cout
	std::cout << "Debug build" << std::endl;
#else
	// Print to the console with cout
	std::cout << "Release build" << std::endl;
#endif
	// Return 0 to signal that the program finished successfully
	return 0;
}

Macro Parentheses

Because macro substitution is purely textual, failing to parenthesize both the macro's parameters and its overall expression can silently break operator precedence -- #define SQUARE(x) x*x computes the wrong result for SQUARE(a+b), expanding to a+b*a+b instead of the intended (a+b)*(a+b).

Example: Macro Parentheses

cpp
// Include std::cout and std::cin
#include <iostream>

// Define the macro SQUARE_BAD (replaced by the preprocessor)
#define SQUARE_BAD(x) x*x
// Define the macro SQUARE_GOOD (replaced by the preprocessor)
#define SQUARE_GOOD(x) ((x) * (x))

// Program execution starts in main()
int main() {
	// Print to the console with cout
	std::cout << SQUARE_BAD(1 + 2) << std::endl;
	// Print to the console with cout
	std::cout << SQUARE_GOOD(1 + 2) << std::endl;
	// Return 0 to signal that the program finished successfully
	return 0;
}
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Writing a function-like macro without parentheses around arguments, so SQUARE(a + 1) expands to the wrong expression.
  2. Putting a semicolon at the end of a #define line, which becomes part of the replacement text and breaks expressions.
  3. Passing an expression with side effects such as MAX(i++, j) to a macro, which may evaluate it twice.
Chapter Summary
  • auto, range-based for loops, and nullptr make code simpler and safer.
  • Move semantics and rvalue references avoid unnecessary copying.
  • constexpr, structured bindings, C++20 modules, and the preprocessor and macros are additional modern and low-level tools.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.