liveBook · Manning

Function-like macro

As explained in Modern C, Third Edition

A function-like macro is a C preprocessor macro invoked with parentheses and arguments that expands its invocation into replacement source text.

A call that runs twice

Consider `#define square_macro(X) (X*X)`. The invocation `square_macro(count())` expands to `(count()*count())`: `count()` runs twice, not once. An inline function taking an `unsigned` parameter evaluates the argument once and provides a typed interface. Prefer that function when the number and types of arguments are known. section 17.0

A function-like macro is invoked with parentheses and arguments, but its replacement is source text, not a function call. A replacement can use an argument more than once or interact with surrounding syntax. Even a C library interface specified as a function may be implemented as a macro. section 8.1.2 section 10.2.1

TAKEAWAY: Make a function-like macro behave syntactically like a function call.

  • Parenthesize parameters and replacement expressions so surrounding operators do not bind unexpectedly. section 10.2.1
  • Do not duplicate an argument that may have side effects; each use in the replacement performs those effects again. section 10.2.1
  • Check how the replacement behaves beside `if` and `else`, trailing semicolons, and commas. A macro that looks like one statement can otherwise change the surrounding control flow. section 10.2.1
 #define ERROR_RETURN(CODE)  \
 do {                        \
   if (CODE) return -1;      \
 } while (false)

The `do { ... } while (false)` wrapper makes a returning macro behave as one statement, avoiding the dangling-`else` problem caused by a bare `if` in its replacement. section 10.2.1

Macros remain useful when a function cannot express the intended interface. They can add compile-time argument checks, provide variable-argument or type-generic interfaces, supply default arguments, and capture information from the invocation site. For example, placing empty string literals around a macro argument accepts a string literal but makes an ordinary character-array name invalid in that position. section 17.0 section 17.2

Tracing is another use of invocation context: `__LINE__`, `__FILE__`, and `__func__` can identify where output originated. Such context access is valuable for debugging but usually undesirable for ordinary functionality, where a function keeps caller and callee separate. Do not assume a `__LINE__` value always fits in an `int`. section 17.3

How Modern C, Third Edition covers it

How deep it goes
Works through textual replacement, repeated evaluation, statement-shape pitfalls, compile-time checks, and invocation-site tracing.
Examples
Contrasts `square_macro(count())` with an inline function, repairs a dangling `else`, and checks a string-literal argument.
What it assumes
Assumes familiarity with C expressions, functions, and control flow; develops the consequences of macro replacement through examples.
What you can do after
Build it from scratch for checks or tracing while recognizing when an inline function avoids duplicated evaluation and provides a typed interface.
How it connects
Connects function-like macros to inline functions, side effects, type-generic interfaces, compile-time argument checking, and debugging traces.

Also explained in

Compare how 3 books explain function-like macro →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Modern C, Third Edition. The section links point to the parts of the book it draws on.