ProgrammingC/Embedded Developer

How are function-like macros implemented and used in C? What pitfalls arise when defining and applying them?

Pass interviews with Hintsage AI assistant

Answer.

Background
Function-like macros are an important part of the C preprocessor, introduced for quickly incorporating repetitive code snippets and simplifying debugging. They are used for small functions, inlining, or optimization.

Issue
Macros do not check types and do not provide full substitution beyond simple text replacement. Errors occur due to missing parentheses and substitution of expressions with side effects.

Solution
Enclose parameters and macro definitions in parentheses, avoid side effects in arguments, and use inline functions for more complex cases.

Code example:

#define MAX(a, b) ((a) > (b) ? (a) : (b)) int x = 5, y = 10; int z = MAX(x++, y++); // Dangerous call!

Key features:

  • Type checking is not performed at compile time
  • It is recommended to enclose all parameters in parentheses to prevent errors
  • Macros are prone to errors when complex expressions containing ++, -- are used.

Trick questions.

Does a macro always fully replace code like a function?

No! A macro is just a text substitution before compilation; it can behave differently than a function if arguments are expressions with side effects.

Can any call (including with ++, --) be used as a macro parameter?

This is extremely dangerous. Side effects will occur multiple times if the parameter is used more than once in the macro.

Code example:

// This call will increase x or y more than by 1 MAX(x++, y++)

How to correctly include parentheses in macro declarations?

Enclose both parameters and the expression inside the macro in parentheses to avoid associativity errors when called within other expressions.

Common mistakes and anti-patterns

  • Not enclosing parameters in parentheses: #define MUL(a, b) a * b
  • Using parameters with side effects
  • A macro with multiple uses of the same parameter

Real-life example

Negative case

A company had a macro #define SQUARE(x) xx defined for many years, and it was used for expressions like SQUARE(a+1). Unexpected errors arose: the expression expanded as a+1a+1, which is different from (a+1)*(a+1).

Pros:

  • Easy and quick to write short macros Cons:
  • Errors noticeable only at runtime
  • Difficult debugging

Positive case

The macro SQUARE was written with full parentheses: #define SQUARE(x) ((x)*(x)). Its usage is standardized and documented.

Pros:

  • No associativity errors
  • Behavior akin to a regular function Cons:
  • No type checking
  • If x++ is passed, the effect will repeat