Skip to content
UniCircle
MediumConceptualPreprocessor

Macros vs inline functions: the SQUARE(x) trap

Question C-PRE-006

Given #define SQUARE(x) x*x, what does SQUARE(a+1) expand to? Explain how the preprocessor works and when you should use an inline function or const instead of a macro.

Show the explanation

The preprocessor runs before the compiler and does plain text substitution. It has no idea about types, scope or evaluation.

Trap 1: missing parentheses. SQUARE(a+1) becomes a+1*a+1, which is 2a + 1, not (a+1)². The fix is to wrap every parameter and the whole body in parentheses:

C
#define SQUARE(x) ((x) * (x))

Trap 2: arguments evaluated more than once. Even the fixed macro expands SQUARE(i++) to ((i++) * (i++)). That modifies i twice without sequencing, which is undefined behaviour. No amount of parentheses fixes this.

A better tool, the inline function

C
static inline int square(int x) { return x * x; }
  • The argument is evaluated exactly once.
  • It is type-checked, and you can step into it in a debugger.
  • The compiler can still inline it, so there is usually no call overhead.

Constants: prefer const int MAX = 100; or an enum { MAX = 100 }; over #define MAX 100 when you want type safety and debugger visibility. (In C, a const int cannot size a file-scope array; an enum constant can.)

What macros are still good for

  • Include guards (#ifndef HEADER_H …) and conditional compilation (#ifdef DEBUG).
  • Using __FILE__ and __LINE__ in logging macros.
  • Stringising (#x) and token pasting (a##b).
  • Multi-statement macros wrapped in do { ... } while (0), so they behave like a single statement after an if.

Original UniCircle question. Last reviewed 22 Oct 2026.