← Back to Blog

The C MIN macro vs. static inline functions: embedded interview notes

  • #C

Three approaches — the original ternary macro, a static inline function, 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

c
#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

c
#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:

c
int m = (a) < (++b) ? (a) : (++b);

✅ Pros

Minimal to write, standard C, no compiler extension required.

❌ Fatal flaws

  1. An argument with a side effect is evaluated several times — the biggest bug here. In MIN(++a, b), if the condition holds, ++a runs twice and the variable increments further than intended.

  2. Without the outer parentheses there is an operator-precedence hazard (the improved version wraps everything in ()).

  3. 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

c
static inline int MIN(int a, int b)
{
    return a < b ? a : b;
}

How it works

It is a real function:

  1. Before the call, every argument is evaluated once, completely;

  2. The results are copied into the parameters (local copies);

  3. Inside the function only the copies are used, so ++ / -- / function calls never run twice.

Test code (compiles and runs as-is with gcc)

c
#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

  1. Standard C, works across compilers (GCC, Clang and MSVC all support it);

  2. Arguments are evaluated once, eliminating the multiple-evaluation bug entirely;

  3. Types are checked — the compiler verifies the argument types for you;

  4. inline suggests 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

c
#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

  1. 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.

  2. typeof(b) _b = b; — the same as the line above, caching the value of b.

  3. (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.

  4. _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

  1. ✅ Generic: works with any compatible types, int / float / long and so on;

  2. ✅ Safe: arguments are evaluated once, so ++x or a function call never runs twice;

  3. ✅ Compile-time type checking — defensive programming that catches mismatches early;

  4. ✅ It is an expression, so it can be assigned or passed to a function.

❌ Cons

  1. Not standard C — a GNU extension, incompatible with MSVC and some compilers;

  2. 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

  1. 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;

  2. static inline is safe and standard, but not generic;

  3. The Linux kernel's MIN macro uses typeof plus 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.