The C MIN macro vs. static inline functions: embedded interview notes
- #C
Three approaches — the original ternary macro, a
static inlinefunction, and the Linux kernel's safe generic macro — with the reasoning, the pitfalls, and a side-by-side comparison. Written for interview review.
Background
The classic interview snippet
#define MIN(a,b) (a)<(b) ? (a) : (b)
int a = 1, b = 2;
int m = MIN(a, ++b);
The core problem: a plain macro is pure text substitution, so when an argument carries a side effect — an increment, a function call — it gets evaluated more than once, and the program quietly misbehaves.
Approach 1: the original ternary macro (the cautionary tale)
Code
#define MIN(a,b) (a)<(b) ? (a) : (b)
How it works
The preprocessor substitutes text directly. It is not a function call, and nothing is copied into parameters.
The snippet above expands to:
int m = (a) < (++b) ? (a) : (++b);
✅ Pros
Minimal to write, standard C, no compiler extension required.
❌ Fatal flaws
-
An argument with a side effect is evaluated several times — the biggest bug here. In
MIN(++a, b), if the condition holds,++aruns twice and the variable increments further than intended. -
Without the outer parentheses there is an operator-precedence hazard (the improved version wraps everything in
()). -
No compile-time type checking: completely incompatible types can be compared, and the mistake stays hidden.
The one-line verdict
Fine for a throwaway demo. Never use it directly in production code or an embedded kernel — this is a textbook dangerous macro.
Approach 2: static inline function (the safe standard-C answer)
Example
static inline int MIN(int a, int b)
{
return a < b ? a : b;
}
How it works
It is a real function:
-
Before the call, every argument is evaluated once, completely;
-
The results are copied into the parameters (local copies);
-
Inside the function only the copies are used, so
++/--/ function calls never run twice.
Test code (compiles and runs as-is with gcc)
#include <stdio.h>
static inline int MIN(int a, int b)
{
return a < b ? a : b;
}
int main(void)
{
int a = 1, b = 2;
int m = MIN(a, ++b);
printf("m = %d, b = %d\n", m, b);
return 0;
}
Output: m=1, b=3 — ++b increments exactly once, which is what you would expect.
✅ Pros
-
Standard C, works across compilers (GCC, Clang and MSVC all support it);
-
Arguments are evaluated once, eliminating the multiple-evaluation bug entirely;
-
Types are checked — the compiler verifies the argument types for you;
-
inlinesuggests the compiler expand it, reducing call overhead.
❌ Cons
The type is fixed: the version above only takes int. Supporting float, long or a custom struct means writing several overloads, and there is no generics.
When to use it
Business logic, standard-C projects, and cross-platform code that avoids GNU extensions. This is the default recommendation.
Approach 3: the Linux kernel's safe generic MIN macro (GCC extension)
Full source
#define MIN(a,b) ({ \
typeof(a) _a = a; \
typeof(b) _b = b; \
(void)(&_a == &_b); \
_a < _b ? _a : _b; \
})
This relies on GCC extensions: the ({ ... }) statement expression plus typeof(). Standard C does not have them and MSVC cannot compile it; it is widespread in the Linux kernel and GCC-based embedded work.
Line by line
-
typeof(a) _a = a;-
typeof()infers the variable's type, which is what makes the macro generic; -
the argument is evaluated once and the result cached in the temporary
_a, ruling out multiple evaluation.
-
-
typeof(b) _b = b;— the same as the line above, caching the value ofb. -
(void)(&_a == &_b);— a favourite interview question.-
It compares pointers to the two temporaries,
&_a == &_b; -
the point is to force a compile-time check that the two types are compatible: comparing pointers to different types raises a warning or an error, catching mismatched comparisons early;
-
(void)discards the result, silencing the "expression result unused" warning, and produces no extra code at run time.
-
-
_a < _b ? _a : _b;— the rule for statement expressions: the last expression becomes the value of the whole macro. The comparison uses the cached copies, so there is no side-effect risk.
✅ Pros
-
✅ Generic: works with any compatible types,
int/float/longand so on; -
✅ Safe: arguments are evaluated once, so
++xor a function call never runs twice; -
✅ Compile-time type checking — defensive programming that catches mismatches early;
-
✅ It is an expression, so it can be assigned or passed to a function.
❌ Cons
-
Not standard C — a GNU extension, incompatible with MSVC and some compilers;
-
Limited to GCC/Clang environments (Linux, embedded gcc).
When to use it
Linux drivers, kernel development, and embedded GCC projects — low-level code that wants both generics and safety.
Side-by-side comparison (the memorise-this version)
| Approach | Multiple evaluation | Generic | Standard C | Compile-time type check | Where it belongs |
|---|---|---|---|---|---|
| Original ternary macro | ❌ High risk | ✅ Generic | ✅ Standard C | ❌ No checking | Simple demos only, not for production |
| static inline function | ✅ No risk | ❌ Fixed type | ✅ Standard C | ✅ Strong checking | Cross-platform application code |
| Kernel statement-expression macro | ✅ No risk | ✅ Generic | ❌ GCC extension | ✅ Compile-time check | Linux kernel, low-level embedded |
Interview summary in three lines
-
The trap in the plain ternary MIN macro: a macro is text substitution, so arguments carrying side effects such as
++get evaluated more than once; -
static inlineis safe and standard, but not generic; -
The Linux kernel's MIN macro uses
typeofplus temporary variables to avoid multiple evaluation, and a pointer comparison for type checking — the price being a GCC extension that does not work with every compiler.