[C/C++] User-defined Macro(1): Basics
Abstract
This post introduces the fundamental concepts and usage patterns of user-defined macros in C and C++. It covers macro constants, function-like macros, and the hazards associated with multiple evaluation.
1. Introduction
In C and C++, macros are handled by the preprocessor, performing simple textual substitution before compilation. While this mechanism can enhance code brevity and reuse, it can also produce unintended side effects and lead to undefined behavior when macro arguments include expressions with side effects. This article is aimed at beginners and explains how macro constants and function-like macros operate, as well as recommended practices to avoid common pitfalls.
2. Organization
- What is a Macro? — Definition and preprocessor-level operation
- Basic Syntax — Defining macro constants using
#define - Macro Function — Implementing function-like macros and the critical importance of parentheses
- Pitfalls of Multiple Evaluation — Case studies of multiple evaluation issues and side-effect risks
- Next — Link to the advanced macro features post
3. Sections
3.1. What is a Macro?
A macro is a preprocessor directive that performs string substitution before the source code is compiled. It is interpreted by the preprocessor, not by the compiler.
3.2. Basic Syntax
#define identifier value
The #define directive is used to define an identifier as a value. The ‘#’ symbol indicates that this line will be preprocessed by the preprocessor.
An identifier defined in this way is called a Macro Constant and can be used as shown below:
Click Run ▶
3.3. Macro Function
A macro can also be defined as a function-like expression:
Click Run ▶
Note that parentheses are essential in the body of a macro function. This is because the preprocessor performs a simple textual substitution and does not evaluate expressoins like a compiler does. Consider the following example, which lacks proper parentheses:
Click Run ▶
In the example above, SQARE(1 + 2) is expanded as 1 + 2 * 1 + 2, which is evaluated as 1 + (2 * 1) + 2 = 5. Likewise, CUBE(1 + 2) becomes 1 + 2 * 1 + 2 * 1 + 2, which also gives an incorrect result.
Therefore, always use parentheses around both the parameters and the entire body of a macro function to ensure correct evalutation.
3.4. Pitfalls of multiple evaluation
Consider the following example:
Click Run ▶
The result is unexpected. This happens because the preprocessor performs simple textual substitution without any concern for side effects or evaluation count.
In the first call: MAX(x++, y) → ((x++) > (y) ? (x++) : (y)) Here, if x++ > y evaluates to true, then x++ is executed again as the return value, meaning x++ is evaluated twice. This causes x to be incremented more than once in a single expression, which can result in undefined behavior in C/C++.