SyntaxStudy
Sign Up
C Function-Like Macros and Pitfalls
C Beginner 1 min read

Function-Like Macros and Pitfalls

Function-like macros accept arguments and substitute them into a template expression. They look like function calls — `MAX(a, b)` — but are expanded inline by the preprocessor with no function-call overhead. Because they are textual substitutions, they work with any type, unlike typed functions. However, this type-agnosticism comes with sharp edges: arguments that have side effects (like `i++`) are expanded every time they appear in the macro body, potentially causing double evaluation. The golden rules for function-like macros are: wrap every parameter in parentheses within the body, and wrap the entire macro body in parentheses. Without this, operator precedence can silently alter the computation. For example, `#define SQUARE(x) x*x` computes `SQUARE(2+3)` as `2+3*2+3 = 11` rather than the expected `25`. The correct form is `#define SQUARE(x) ((x)*(x))`. The `do { ... } while(0)` idiom is the standard way to write multi-statement macros safely. Wrapping statements in a plain block `{ ... }` breaks when the macro is used in an `if/else` without braces. The `do-while(0)` trick creates a construct that behaves like a single statement and requires a semicolon, matching the syntax of a function call.
Example
#include <stdio.h>

/* Correct: all params and result parenthesised */
#define MAX(a, b)       (((a) > (b)) ? (a) : (b))
#define MIN(a, b)       (((a) < (b)) ? (a) : (b))
#define ABS(x)          (((x) < 0) ? -(x) : (x))
#define CLAMP(v, lo, hi) (MAX((lo), MIN((v), (hi))))

/* Multi-statement macro using do-while(0) */
#define SWAP(type, a, b) do { \
    type _tmp = (a);          \
    (a) = (b);                \
    (b) = _tmp;               \
} while (0)

/* Stringification with # operator */
#define STRINGIFY(x)    #x
#define TO_STRING(x)    STRINGIFY(x)

/* Token pasting with ## operator */
#define MAKE_VAR(prefix, num)  prefix##num

int main(void)
{
    printf("MAX(3,7)      = %d\n", MAX(3, 7));      /* 7  */
    printf("MIN(3,7)      = %d\n", MIN(3, 7));      /* 3  */
    printf("ABS(-5)       = %d\n", ABS(-5));        /* 5  */
    printf("CLAMP(15,0,10)= %d\n", CLAMP(15,0,10)); /* 10 */

    int x = 10, y = 20;
    SWAP(int, x, y);
    printf("After SWAP: x=%d y=%d\n", x, y);        /* 20, 10 */

    /* Double-evaluation danger (illustrative — avoid in real code) */
    int i = 3;
    /* MAX(i++, 5) would increment i TWICE — undefined behaviour */
    /* Use a temporary variable instead: */
    int safe = i;
    printf("safe MAX: %d\n", MAX(safe, 5));

    printf("STRINGIFY: %s\n", STRINGIFY(Hello World));
    printf("Line: "  TO_STRING(__LINE__) "\n");

    int MAKE_VAR(data, 42) = 999;
    printf("data42 = %d\n", data42);

    return 0;
}