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:
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.
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
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
elsethat matches thatif
This is syntactically correct.
Step 3: Runtime behavior
If cond is true:
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 { \
((ptr) == NULL) return; \
} while (0)
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
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.
Comparisons
| Approach | Expands to one statement? | Safe in if/else? | Typical use | Notes |
|---|---|---|---|---|
#define FOO(X) f(X); g(X) | No | No | Rarely appropriate | Can break control flow |
#define FOO(X) do { f(X); g(X); } while (0) | Yes | Yes | Standard multi-statement macro | Most common safe pattern |
#define FOO(X) if (1) { f(X); g(X); } else | Mostly statement-like | Sometimes used | Older or less common style | Harder to read |
inline function | Yes |
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
inlinefunctions when preprocessor features are not needed - be careful with side effects
- avoid surprising
break,continue, orreturnunless intentional
Edge case reminder
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.
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
- Create a macro that prints two log messages.
- Create a macro that frees a pointer and sets it to
NULL. - Use both macros inside
if/elsestatements. - Ensure the program compiles and runs correctly.
- Wrap each multi-statement macro safely.
Keep learning
Related questions
Building More Fault-Tolerant Embedded C++ Applications for Radiation-Prone ARM Systems
Learn practical C++ and compile-time techniques to reduce soft-error damage in embedded ARM systems exposed to radiation.
Definition vs Declaration in C and C++: What’s the Difference?
Learn the difference between declarations and definitions in C and C++ with simple examples, common mistakes, and practical usage.
Difference Between #include <...> and #include "..." in C and C++
Learn the difference between #include with angle brackets and quotes in C and C++, including search paths, examples, and common mistakes.