CFunction-Like Macros

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.

C
#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.

The classic broken macro

C
#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! */
Without parentheses, the `+` inside the argument gets mixed into the multiplication according to normal operator precedence, producing a completely wrong result with no compiler warning.

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:

C
#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 redeeming quality
A well-parenthesized macro has genuinely useful properties: zero call overhead and the ability to operate on any type generically, without writing a separate version per type. That is exactly why macros persisted in C even though they are riskier than functions.
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.

MAX(a++, b) evaluates a++ twice

C
#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. */
A real `int max(int a, int b)` function would evaluate `a++` exactly once, because arguments to a function are evaluated before the call, not re-inserted as text.
What Modern C Prefers Instead
  • static inline functions — 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 intact

  • Reserve function-like macros for cases that genuinely cannot be expressed as a function, such as stringizing/token-pasting tricks or conditional-compilation helpers

Note
Seeing `#define MAX(a, b) ...` in a modern C11+ codebase is often a sign it predates `static inline` becoming common practice, or that it targets a strict C89 environment lacking `inline` support.