c language · high

C: Preprocessor Macro Pitfalls

C preprocessor macros are pure textual substitution with no awareness of operator precedence, scope, or types, which creates several classic bugs. Without parentheses, #define SQ(x) x*x turns SQ(a+b) into a+b*a+b (parsed as a + b*a + b), so you must parenthesize every parameter and the entire expansion: ((x)*(x)) — the single most important macro habit. Even when parenthesized, a macro substitutes its argument as text, so side-effecting arguments are evaluated multiple times: SQ((i++)) becomes ((i++)*(i++)), incrementing i twice (undefined behavior), and MAX(f(), g()) calls each function twice — a real, possibly inline, function evaluates each argument exactly once and should be preferred whenever it can do the job. Macros do retain two unique powers: # stringizes an argument into a string literal (great for assertion and logging macros that print the expression text), and ## pastes tokens to build new identifiers (useful for code generation). Remaining gotchas include trailing semicolons and multi-statement macros, which should be wrapped in do { ... } while (0) so they behave as a single statement, and the fact that macros ignore scope — by convention they are UPPER_CASE to flag the risk.

🔑 Key line

C macro pitfalls: textual substitution has no precedence (parenthesize every param and the whole body: ((x)*(x))) and re-evaluates side-effecting arguments (SQ(i++) increments twice — prefer inline functions); # stringizes, ## token-pastes, and wrap multi-statement macros in do{..}while(0).

The code

#define SQ(x) x * x // BAD: no parentheses
SQ(a + b) -> a + b * a + b // = a + (b*a) + b — wrong!
#define SQ2(x) ((x) * (x)) // better, but...
SQ2(i++) -> ((i++) * (i++)) // i++ TWICE -> UB / double effect
#define MAX(a,b) ((a) < (b) ? (b) : (a)) // evaluates args twice too
#define STR(x) #x // stringize: STR(hi) -> "hi"
#define CAT(a,b) a##b // token paste: CAT(x,1) -> x1

What this lesson walks through

  1. 01Parenthesize everything
  2. 02Multiple evaluation of arguments
  3. 03Gotcha — multi-statement macros need do/while(0)
  4. 04Stringize (#) and token-paste (##)

Macros are textual substitution with no notion of precedence, so #define SQ(x) x*x turns SQ(a+b) into a+b*a+b, which parses as a + (b*a) + b — not (a+b)^2. The fix is to parenthesize every parameter AND the whole expansion: #define SQ(x) ((x)*(x)). This single habit prevents the majority of macro bugs.

See it animated — step by step, at your own pace

Unlock the full interactive walkthrough of C: Preprocessor Macro Pitfalls and 100+ animated C++ interview lessons.

← Previous
C: Function Pointers & Callbacks
Next →
C: char Signedness & sizeof Quirks