Question

Why C/C++ Macros Use do { ... } while (0) and if/else Wrappers

ccppc-preprocessorc++-faq

Question

In C and C++ code, many multi-statement macros are wrapped in patterns that look unnecessary, such as:

#define FOO(X) do { f(X); g(X); } while (0)
#define FOO(X) if (1) { f(X); g(X); } else

At first glance, the do ... while (0) loop appears meaningless because it runs only once. Why is this pattern used instead of writing the macro more directly like this?

#define FOO(X) f(X); g(X)

What problem does the wrapper solve, and why is it helpful in real C/C++ code?

Short Answer

By the end of this page, you will understand why multi-statement macros in C and C++ are often wrapped in do { ... } while (0), what bugs this avoids, how it behaves like a single statement, and why it is usually safer than writing raw multiple statements directly in a macro.

Concept

Multi-statement macros are a preprocessor feature in C and C++. The preprocessor performs text substitution before compilation. That means a macro is not a function and does not follow normal statement boundaries unless you explicitly make it do so.

A macro like this:

#define FOO(X) f(X); g(X)

expands into two separate statements. That can cause problems when the macro is used in places where the programmer expects it to behave like a single statement.

For example:

if (cond)
    FOO(42);
else
    h();

After macro expansion, this becomes:

if (cond)
    f(42); g(42);
else
    h();

Now the if only controls f(42);. The g(42); is a separate statement, and the else no longer matches correctly. This can cause compilation errors or logic bugs.

The do { ... } while (0) wrapper solves this by making the whole macro expand to exactly one statement:

#define FOO(X) do { f(X); g(X); } while (0)

Now this usage works correctly:

if (cond)
    FOO(42);
else
    h();

because it expands to:

if (cond)
    do { f(42); g(42); } while (0);
else
    h();

That is valid syntax, and the entire macro acts like a single statement.

This matters in real programming because macros are often used for:

  • logging
  • error handling
  • locking/unlocking
  • debug assertions
  • platform-specific code

If a macro can break control flow depending on where it is used, it becomes dangerous and hard to maintain.

Mental Model

Think of a macro as a piece of paper that gets pasted directly into your code before the compiler reads it.

If the macro contains multiple loose statements, pasting it into an if statement is like pasting two separate instructions where only one instruction was expected.

The do { ... } while (0) wrapper is like putting all of those instructions into a single sealed box. The compiler sees one statement, even though the box contains multiple lines inside.

So the key idea is:

  • raw macro body = several loose pieces
  • wrapped macro body = one safe unit

That is why the wrapper exists.

Take Quiz

Syntax and Examples

Core pattern

#define MACRO_NAME(args) do { \
    statement1;            \
    statement2;            \
} while (0)

This form is used for macros that need more than one statement.

Unsafe version

#define PRINT_BOTH(a, b) printf("%d\n", (a)); printf("%d\n", (b))

Usage:

if (ready)
    PRINT_BOTH(1, 2);
else
    printf("not ready\n");

Expansion:

if (ready)
    printf("%d\n", (1)); printf("%d\n", (2));
else
    printf("not ready\n");

This is broken because only the first printf belongs to the if.

Safe version

#define PRINT_BOTH(a, b) do {      \
    printf("%d\n", (a));          \
    printf("%d\n", (b));          \
} while (0)

Usage:

if (ready)
    PRINT_BOTH(1, 2);
else
    printf("not ready\n");

Expansion:

if (ready)
    do {
        printf("%d\n", (1));
        printf("%d\n", (2));
    } while (0);
else
    printf("not ready\n");

Now the whole macro is treated as one statement.

Why while (0)?

Because the body should run exactly once.

  • do allows a block with a trailing semicolon after the macro call
  • while (0) ensures the loop never repeats

About the if (1) { ... } else form

You may also see:

#define FOO(X) if (1) { f(X); g(X); } else

This is another attempt to force macro usage into statement-like syntax, but do { ... } while (0) is usually clearer and more widely accepted for multi-statement macros.

Step by Step Execution

Consider this macro:

#define FOO(X) do { f(X); g(X); } while (0)

And this code:

if (cond)
    FOO(10);
else
    h();

Step 1: Preprocessor expansion

The preprocessor replaces FOO(10) with the macro body:

if (cond)
    do { f(10); g(10); } while (0);
else
    h();

Step 2: Compiler reads the if

The compiler sees:

  • an if (cond)
  • one statement after it: do { f(10); g(10); } while (0);
  • an else that matches that if

This is syntactically correct.

Step 3: Runtime behavior

If cond is true:

  1. enter the do block
  2. call f(10)
  3. call g(10)
  4. check while (0)
  5. stop immediately because the condition is false

If cond is false:

  1. skip the macro entirely
  2. run h()

Compare with the unsafe version

#define FOO(X) f(X); g(X)

Expansion:

if (cond)
    f(10); g(10);
else
    h();

Now the compiler interprets it roughly as:

if (cond)
    f(10);
g(10);
else
    h();

That is invalid, because the else no longer has the matching if structure you intended.

Real World Use Cases

Logging macros

#define LOG_ERROR(msg) do {         \
    fprintf(stderr, "ERROR: %s\n", msg); \
    write_to_log_file(msg);         \
} while (0)

A logging macro may need multiple statements, but you still want to use it like a single statement.

Resource handling

#define CLOSE_BOTH(a, b) do { \
    fclose(a);                \
    fclose(b);                \
} while (0)

Without wrapping, this could break inside conditionals.

Debug macros

#define TRACE_VALUE(x) do {                      \
    printf("%s = %d\n", #x, (x));              \
    fflush(stdout);                              \
} while (0)

Debug helpers often perform more than one action.

Platform abstraction

Macros sometimes hide platform-specific implementation details:

#define LOCK_MUTEX(m) do {   \
    platform_lock(m);        \
    log_lock_event(m);       \
} while (0)

Error-handling helpers

#define RETURN_IF_NULL(ptr) do { \
    if ((ptr) == NULL) return;   \
} while (0)

This still behaves like one statement when used in a function body.

Take Quiz

Real Codebase Usage

In real codebases, multi-statement macros are usually used carefully and for specific reasons.

Common patterns

Guard-style macros

#define CHECK_ALLOC(p) do {                  \
    if ((p) == NULL) {                       \
        fprintf(stderr, "Out of memory\n"); \
        return -1;                           \
    }                                        \
} while (0)

This lets the macro be used safely in an if block or as a standalone statement.

Logging and diagnostics

#define DEBUG(msg) do {                    \
    fprintf(stderr, "DEBUG: %s\n", msg); \
} while (0)

Cleanup helpers

#define FREE_AND_NULL(p) do { \
    free(p);                  \
    (p) = NULL;               \
} while (0)

Why not always use functions?

Developers often prefer functions when possible, but macros are still used when they need:

  • type-generic behavior in C
  • compile-time stringification like #x
  • token pasting with ##
  • conditional compilation
  • very lightweight wrappers around control flow

Best practice

If a macro expands to multiple statements, wrap it in do { ... } while (0) so callers can safely write:

FOO(x);

with a normal trailing semicolon.

Common Mistakes

1. Writing multi-statement macros without a wrapper

Broken:

#define SET_FLAGS(x) x |= 1; x |= 2

Problem:

  • breaks in if/else
  • behaves like two separate statements

Better:

#define SET_FLAGS(x) do { \
    (x) |= 1;            \
    (x) |= 2;            \
} while (0)

2. Forgetting the trailing semicolon at the call site

A wrapped macro is intended to be used like this:

FOO(x);

not:

FOO(x)

Using the semicolon keeps macro calls consistent with normal statements.

3. Confusing macros with functions

Macros are text substitution, not real functions.

For example:

#define SQUARE(x) ((x) * (x))

This can evaluate x more than once if x has side effects.

4. Missing parentheses around arguments

Broken:

#define DOUBLE(x) x + x

Usage:

int y = DOUBLE(1 + 2);

Expansion:

int y = 1 + 2 + 1 + 2;

Safer:

#define DOUBLE(x) ((x) + (x))

5. Using if (1) { ... } else without understanding it

This style exists, but it is less readable for many programmers and can be surprising. do { ... } while (0) is usually easier to recognize as the standard safe wrapper.

6. Putting break or continue inside such macros carelessly

Inside do { ... } while (0), a break breaks out of the macro wrapper, not an outer loop you might have expected. That can be confusing.

Example:

#define MAYBE_STOP() do { \
    if (error) break;     \
} while (0)

This only breaks the internal do block.

Comparisons

ApproachExpands to one statement?Safe in if/else?Typical useNotes
#define FOO(X) f(X); g(X)NoNoRarely appropriateCan break control flow
#define FOO(X) do { f(X); g(X); } while (0)YesYesStandard multi-statement macroMost common safe pattern
#define FOO(X) if (1) { f(X); g(X); } elseMostly statement-likeSometimes usedOlder or less common styleHarder to read
inline functionYesYesPreferred when possibleType-checked, safer, debugger-friendly

Macro vs inline function

FeatureMacroInline function
Preprocessor substitutionYesNo
Type checkingNoYes
Can contain multiple statements safelyYes, with wrapperYes
Can stringify arguments (#x)YesNo
Can avoid repeated evaluation automaticallyNoYes

When possible, prefer an inline function. Use a macro when you specifically need preprocessor features.

Take Quiz

Cheat Sheet

Safe multi-statement macro pattern

#define NAME(args) do { \
    statement1;         \
    statement2;         \
} while (0)

Why use it?

  • makes the macro behave like one statement
  • safe inside if/else
  • allows a normal trailing semicolon

Unsafe pattern

#define NAME(args) statement1; statement2

This expands to multiple loose statements.

Typical usage

if (cond)
    NAME(x);
else
    other();

Rules

  • wrap multi-statement macros in do { ... } while (0)
  • parenthesize macro arguments when used in expressions
  • prefer inline functions when preprocessor features are not needed
  • be careful with side effects
  • avoid surprising break, continue, or return unless intentional

Edge case reminder

while (0) is not for looping. It is only there to create a single statement wrapper.

FAQ

Why does do { ... } while (0) use a loop if it never loops?

Because the goal is not repetition. The goal is to package multiple statements into a single statement that works cleanly with a trailing semicolon.

Why not just use braces { ... } in the macro?

A bare block does not always behave as neatly as do { ... } while (0) when used with a trailing semicolon in all contexts. The do form is the conventional statement wrapper.

Is if (1) { ... } else equivalent?

It is a similar trick, but do { ... } while (0) is generally clearer and more common for multi-statement macros.

Should I use macros or inline functions?

Use inline functions when possible. Use macros only when you need preprocessor-specific features like stringification, token pasting, or conditional compilation.

Can do { ... } while (0) affect performance?

No meaningful runtime loop happens because the condition is always false. Compilers optimize this away.

Can I put return inside such a macro?

Yes, but be careful. It returns from the function where the macro is expanded, which can make control flow harder to read.

Does this pattern work in both C and C++?

Yes. It is a common idiom in both languages for safe multi-statement macros.

Take Quiz

Mini Project

Description

Build a small C program that uses a logging macro and a cleanup macro inside if/else statements. This demonstrates why multi-statement macros must behave like a single statement in real code.

Goal

Create and use safe multi-statement macros that work correctly inside conditional logic.

Requirements

  1. Create a macro that prints two log messages.
  2. Create a macro that frees a pointer and sets it to NULL.
  3. Use both macros inside if/else statements.
  4. Ensure the program compiles and runs correctly.
  5. Wrap each multi-statement macro safely.
Take Quiz

Keep learning

Related questions

View more