While(0), Why Macros Use It?

The do{…}while(0) statement is used to allow a macro to behave like a function. It is quite easy to find a #define statement that contains do{…}while(0) in it. This article is a follow up for a question left in How To Debug Webassemly?.

Why Not Use a Function Instead of a Macro?

If we want to make a macro look like a function, why not use a function then? There are several reasons. The particular reason we use a macro instead of a function for the debug implementation is that, by being a macro, it can get information about the location the macro is placed in the code, which is very important for debugging. The macro is location dependent, so we can log its location properties like filename, function, line number, etc.

Imagine you have several functions in you code that checks input parameters and log a message every time input values are out of expected range, finding the location of a logged message is much easier if the message already gives you its coordinates like file, line number, etc…

Macro Substitutions and -E Compilation Option

There is a step before compiling your code where the compiler perform all macro/define substitutions. This step is called preprocessing. The substitutions can get complicated and difficult to figure out what is the actual code being compiled. The -E compilation option comes to rescue, it instructs the compiler to output the source code after all substitutions. No actual compilation happens, just the substituitions. This option is extremely useful when debugging complex macros/defines. The -E option is going to be used on all compilations in this article.

Example Macro

The following simplified debug macro is used to help us understand why the while(0) construct is so helpful. The macro calls the Dbg_Printf() function with the passed in TEXT if the DEBUG_WASM_ENABLED variable is not zero:


#define DBG_LOG(TEXT) \  
do \  
{ \  
  if (DEBUG_WASM_ENABLED) Dbg_Printf(TEXT); \  
} while (0)  

Using the Macro

The following main.c file contains the Test() function that uses the DBG_LOG macro and is the base for this article.

A friend of mine once said that anything named Test brings bad luck, use it at your own risk…


#include <emscripten.h>  
#include "debug-wasm.h"  

void Test(void)  
{  
    DBG_LOG("PASSED HERE!");  
}  

Compiling it

Note the use of -E option. The output file uses the .i extension which is a common denomination for a preprocessor file.


emcc -E main.c -s WASM=1 -o main.i  

After preprocessing and modifying its indentation for clarification:


void Test(void) 
{ 
 do 
 { 
   if (DEBUG_WASM_ENABLED) Dbg_Printf("PASSED HERE!"); 
 } while (0); 
} 

As you can see, it is straight forward code, the do{…}while(0) does not do anything special, it actually can be deleted without any harm. But that is not always the case. Lets see a more revealing example.

Still not Convinced, Remove the do{…}while(0) statement and Show me The Problem

Lets remove its do{…}while(0) statement:


#define DBG_LOG(TEXT) if (DEBUG_WASM_ENABLED) Dbg_Printf(TEXT)  

It works as well for our previous example:


void Test(void) 
{ 
 if (DEBUG_WASM_ENABLED) Dbg_Printf("PASSED HERE!"); 
} 

Now lets modify the Test() function to have an if statement and place our macro inside the if:


void Test(char a)  
{
    if( a!=0 )  
      DBG_LOG("a is NOT zero!");  
    else  
      DBG_LOG("a is zero");  
}

Following is the Test() function after it is preprocessed. I have added some braces to clarify what is going on because it is confusing!


void Test(char a) 
{  
  if( a!=0 )  
  { 
    if (DEBUG_WASM_ENABLED)  
      Dbg_Printf("a is NOT zero!"); 
    else 
      if (DEBUG_WASM_ENABLED)  
        Dbg_Printf("a is zero");  
  } 
} 

The preprocessed function shows the else was misplaced! The else should belong to if( a!=0 ), but, after the substitutions, it belongs to if (DEBUG_WASM_ENABLED). As you can see the substituted code does not do what was intended for it. This kind of problem is very, very difficult to debug.

The problem comes from the fact that the faulty macro has an if that is dangling and ready to attach itself to any else it finds! It will cause a problem every time an else follows, it attaches itself to a following else. The same error is not seen with the macro that uses the do{…}while(0) because it does not attach itself to any other statement.

Why Not Use a Simple {} Block?

This question was asked by axilmar in Reddit and answered by Y_less. Fair enough, but it runs into problems because, to behave like a function, the macro must accept the semicolon(;) that follows a function call. Lets see it:

Macro using {} instead of do{…}while(0)


#define DBG_LOG(TEXT) \
{ \
    if (DEBUG_WASM_ENABLED) Dbg_Printf(TEXT); \
}

Preprocessed code


void Test(char a)
{
 if( a!=0 )
   { if ((1)) Dbg_Printf("a is NOT zero!"); };
 else
   { if ((1)) Dbg_Printf("a is zero"); };
}

Error generated during the compilation


main.c:15:2: error: expected expression
        else
        ^
1 error generated.
ERROR:root:compiler frontend failed to generate LLVM bitcode, halting

The extra semicolon ends the if( a!=0 ) statement! The compiler error out on the following else because it is not a valid statement.

What Other Weird Macro Is Out There?

Throughout the years I have seen many creative macro implementations. Macros are powerful and can change a program to the point where we cant recognize in what language it was written with. For example, take a look at the following macro that creates a handy loop statement:

What language is this written in?


loop(i, 10) 
{ 
   We_Hope_In_Doing_Something_Useful(); 
} 

Here is its C macro definition:


#define loop(x,n) for(int x = 0; x < n; ++x) 

Conclusion

The world of Macros are powerful, tricky and, complex. No obvious constructs are used in order to get to the intended implementation, but this unorthodox use of the language can make it very hard to understand. Using the -E compilation option can help figure out what is the actual code being seen by the compiler.


Leave a message below. Webassembly is evolving rapidly, please let me know if this post got outdated.

Enjoyed this post?

Don't miss new posts: Share it with your friends:

You may also like...

1 Response

Leave a Reply

Your email address will not be published. Required fields are marked *