The Secret of C++ Macros: Why do-while(0) and if-else Are Needed in Macros and 3 Fatal Errors

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Macros in C++: Dangerous Beauty or Necessary Evil?

\n\n

Anyone who has ever written in C++ has encountered macros. On one hand, they are a powerful tool for metaprogramming that can reduce the amount of boilerplate code. On the other hand, macros are notorious for their insidiousness. Just miss one semicolon or place parentheses incorrectly, and the code turns into an unpredictable monster.

\n\n

But there is a special category of macros in the C++ world that looks frankly strange. Have you ever seen code like this?

\n\n
#define SAFE_DELETE(ptr) do { delete (ptr); (ptr) = nullptr; } while(0)
\n\n

It seems logical: delete the pointer and set it to null. But why is this meaningless do-while(0) here? A loop that is guaranteed to execute exactly once? It looks like redundancy bordering on absurdity. Or here's another example with if-else:

\n\n
#define LOG_ERROR(msg) if (g_logging) std::cerr << (msg)
\n\n

It seems simple. But this is exactly where the rake lies that thousands of developers have stepped on. This question on Stack Overflow has gathered tens of thousands of views, and today we will break down why such constructs are not a bug but a feature, and what C++ macro errors they prevent.

\n\n

Why Are Macros Not Functions? The Main Problem

\n\n

Before diving into the intricacies of do-while(0), let's recall how a macro differs from a regular function. In C++, a macro is a textual substitution. The preprocessor simply copies the macro body to the place where it is called. No type checks, no scopes — just a string replacement.

\n\n

It is because of this that classic errors in C++ macros arise. Let's look at a simple example:

\n\n
#define SQUARE(x) x * x\n\nint main() {\n    int a = 5;\n    int result = SQUARE(a + 1); // Expect 36, get 11\n    std::cout << result; // Will output 11\n    return 0;\n}
\n\n

What happened? The macro expanded to a + 1 * a + 1. Due to operator precedence, multiplication was performed before addition. The first error is the lack of parentheses. Let's fix it:

\n\n
#define SQUARE(x) ((x) * (x))
\n\n

Now it works. But these are just the beginning. Real problems start when a macro contains multiple statements or control structures.

\n\n

The do-while(0) Idiom: A Shield Against Syntactic Disasters

\n\n

Let's return to our SAFE_DELETE. Why not write the macro simpler, without this strange loop?

\n\n
#define SAFE_DELETE(ptr) delete (ptr); (ptr) = nullptr;
\n\n

It seems logical. But imagine using it in a context like this:

\n\n
if (ptr != nullptr)\n    SAFE_DELETE(ptr);\nelse\n    std::cout << "Pointer is null";
\n\n

After substitution, the preprocessor will turn this into:

\n\n
if (ptr != nullptr)\n    delete (ptr);\n    (ptr) = nullptr;\nelse\n    std::cout << "Pointer is null";
\n\n

And there it is — the bug! The else no longer belongs to the if because a second statement appeared between them. The compiler will throw an error, but the point is clear: the macro "broke" the control structure. This is exactly the case where do-while(0) is needed. It turns multiple statements into one compound statement, preserving the logic:

\n\n
if (ptr != nullptr)\n    do { delete (ptr); (ptr) = nullptr; } while(0);\nelse\n    std::cout << "Pointer is null";
\n\n

Now everything works correctly. The macro idiom do-while(0) is a standard technique that guarantees the macro will behave as a single statement.

\n\n

Why do-while(0) and Not Just a Block { }?

\n\n

Good question! You could just use curly braces:

\n\n
#define SAFE_DELETE(ptr) { delete (ptr); (ptr) = nullptr; }
\n\n

Let's try using

Blogs

Book Recommendations