Function-Like Macros
Beyond simple constants, #define can also create function-like macros — substitutions that take arguments and paste them into a text template, syntactically resembling a function call.
#define SQUARE(x) ((x) * (x))
#include <stdio.h>
int main(void)
{
printf("%d\n", SQUARE(5)); /* expands to ((5) * (5)) */
printf("%d\n", SQUARE(1 + 2)); /* expands to ((1 + 2)*(1 + 2)) */
return 0;
}Parenthesize Everything — Always
Because a function-like macro is still just text substitution, the argument you pass gets pasted in verbatim, wherever the parameter name appears in the template. If the template or the parameter uses is not carefully parenthesized, operator precedence can silently break the expansion.
#define SQUARE(x) x*x int result = SQUARE(1 + 2); /* Expands to: 1 + 2*1 + 2 = 1 + 2 + 2 = 5 NOT 9, even though SQUARE(3) would have been 9! */
The fix is to parenthesize both each individual parameter and the macro body as a whole, so the substituted text is always isolated from whatever surrounds it:
#define SQUARE(x) ((x) * (x)) int result = SQUARE(1 + 2); /* Expands to: ((1 + 2) * (1 + 2)) = (3 * 3) = 9 -- correct! */
Macros vs Real Functions
Aspect | Function-Like Macro | Real Function |
|---|---|---|
Type checking | None — pure text substitution | Full parameter and return type checking |
Debugging | Cannot step into it; expands inline before compile | Can be stepped into and breakpointed |
Call overhead | None — no function call at all | Small overhead (unless inlined by the compiler) |
Genericity | Works across any type, since it is just text | Requires a separate overload/version per type in plain C |
Side-effect safety | Dangerous — an argument can be evaluated more than once | Safe — each argument is evaluated exactly once |
Scoping | None — a preprocessor-wide text rule | Normal C scoping rules apply |
The Side-Effect Danger: Multiple Evaluation
Even a perfectly parenthesized macro has a hazard that a real function does not: if a parameter appears more than once in the macro body, the argument expression is evaluated once for every occurrence. If that argument has a side effect — like ++ — it fires multiple times.
#define MAX(a, b) ((a) > (b) ? (a) : (b)) int a = 5, b = 10; int m = MAX(a++, b); /* Expands to: ((a++) > (b) ? (a++) : (b)) If a++ > b is false, a++ still runs again in the ':' branch, so 'a' ends up incremented TWICE instead of once. */
What Modern C Prefers Instead
static inlinefunctions — real type-checked functions that the compiler is free to inline, giving you macro-like performance with none of the text-substitution hazards_Generic(C11) — lets you write a single macro-like call spelling that dispatches to different type-specific real functions, keeping type safety intactReserve function-like macros for cases that genuinely cannot be expressed as a function, such as stringizing/token-pasting tricks or conditional-compilation helpers