Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

A function-like macro is a preprocessor directive that accepts arguments and performs inline text substitution before the compilation phase begins. Unlike standard C functions, macros do not execute at runtime, allocate stack frames, or enforce type safety; they strictly operate via token replacement handled by the C Preprocessor (CPP).

Syntax

#define MACRO_NAME(param1, param2, ...) replacement_list
Crucial Syntax Rule: There must be zero whitespace between the MACRO_NAME and the opening parenthesis (. If a space is present, the preprocessor interprets it as an object-like macro, treating the parenthesis and parameters as part of the literal replacement_list. Variadic Macros: The ellipsis (...) indicates a variable number of arguments. Within the replacement_list, these variadic arguments are accessed and substituted using the special identifier __VA_ARGS__.
#define LOG_ERROR(format, ...) fprintf(stderr, format, __VA_ARGS__)

Preprocessor Mechanics

When the CPP encounters a macro invocation, it performs the following sequence:
  1. Identifies the macro identifier and parses its arguments.
  2. Argument Prescan: Each argument is fully macro-expanded in isolation before being substituted into the replacement_list. Exception: Arguments that are operands to the stringification (#) or token pasting (##) operators bypass this prescan phase and are not expanded.
  3. Substitutes the expanded arguments into the corresponding parameters within the replacement_list.
  4. Replaces the original macro invocation in the source code with the expanded replacement_list.
  5. Rescans the newly expanded text for further macro expansions.
Note: The prescan exception is the exact reason why two-level “helper” macros are required when a developer needs to stringify or concatenate the expanded results of other macros.

Special Preprocessor Operators

Macros frequently utilize two preprocessor operators to manipulate tokens during the expansion phase:

Stringification (#)

Exclusive to function-like macros, the # operator converts a macro parameter into a string literal. It escapes embedded quotes and backslashes automatically.
#define STRINGIFY(x) #x

// Invocation: STRINGIFY(int a = 0;)
// Expansion:  "int a = 0;"

Token Pasting (##)

Available in both function-like and object-like macros, the ## operator concatenates two adjacent tokens into a single, valid C token. It is evaluated before the rescanning phase.
#define CONCAT(a, b) a ## b

// Invocation: int CONCAT(var, 1) = 5;
// Expansion:  int var1 = 5;

Structural Integrity and Pitfalls

Because macros are naive text substitutions, they are highly susceptible to precedence anomalies, evaluation errors, and scope leaks. Understanding these mechanical flaws is critical for writing safe macros.

1. Operator Precedence Inversion

Arguments passed to a macro can contain operators. If parameters in the replacement_list are not strictly parenthesized, the surrounding expression context will alter the intended order of operations.
// Unsafe Definition
#define MULTIPLY(a, b) a * b
// Invocation: MULTIPLY(2 + 3, 4) 
// Expands to: 2 + 3 * 4 (Evaluates to 14, not 20)

// Safe Definition
#define MULTIPLY(a, b) ((a) * (b))
// Invocation: MULTIPLY(2 + 3, 4) 
// Expands to: ((2 + 3) * (4)) (Evaluates to 20)

2. Multiple Evaluation (Side Effects)

If a parameter appears multiple times in the replacement_list, the argument passed to it will be evaluated multiple times. This causes undefined or unintended behavior if the argument contains side effects (e.g., increment operators, function calls, or volatile reads).
#define MAX(a, b) ((a) > (b) ? (a) : (b))

// Invocation: MAX(x++, y)
// Expands to: ((x++) > (y) ? (x++) : (y))
// Result: 'x' is incremented twice if x > y.

3. Multi-Statement Macros and Scope

When a macro contains multiple statements, expanding it inside control flow structures (like an unbraced if statement) breaks the Abstract Syntax Tree (AST) logic. To safely encapsulate multiple statements, macros are wrapped in a do { ... } while(0) construct. This ensures the macro expands into a single statement block that absorbs a terminating semicolon.
// Unsafe Definition
#define SWAP(x, y) int tmp = x; x = y; y = tmp;
// Fails if used as: if (condition) SWAP(a, b); else ...

// Structurally Safe Definition
#define SWAP(x, y) do { \
    int tmp = (x);      \
    (x) = (y);          \
    (y) = tmp;          \
} while(0)

4. Variable Capture (Macro Hygiene)

Even when safely wrapped in a do { ... } while(0) block, macros that declare local variables are vulnerable to variable capture (a lack of macro hygiene). If the caller passes an argument with the exact same identifier as the macro’s internal variable, it results in shadowing or undefined behavior.
// Using the Structurally Safe SWAP definition from above:
// Invocation: SWAP(tmp, b);
// Expands to: 
do { 
    int tmp = (tmp); 
    (tmp) = (b); 
    (b) = tmp; 
} while(0)
In this scenario, int tmp = (tmp); initializes the local variable with its own uninitialized value, destroying the intended logic. Mitigating this in standard C requires using highly obscure naming conventions for internal macro variables (e.g., _swap_internal_tmp_) to minimize collision probability.
Master C with Deep Grasping Methodology!Learn More