Why use apparently meaningless do-while and if-else statements in macros?

前端 未结 9 1928
轻奢々
轻奢々 2020-11-21 04:00

In many C/C++ macros I\'m seeing the code of the macro wrapped in what seems like a meaningless do while loop. Here are examples.

#define FOO(X         


        
9条回答
  •  别跟我提以往
    2020-11-21 05:02

    Macros are copy/pasted pieces of text the pre-processor will put in the genuine code; the macro's author hopes the replacement will produce valid code.

    There are three good "tips" to succeed in that:

    Help the macro behave like genuine code

    Normal code is usually ended by a semi-colon. Should the user view code not needing one...

    doSomething(1) ;
    DO_SOMETHING_ELSE(2)  // <== Hey? What's this?
    doSomethingElseAgain(3) ;
    

    This means the user expects the compiler to produce an error if the semi-colon is absent.

    But the real real good reason is that at some time, the macro's author will perhaps need to replace the macro with a genuine function (perhaps inlined). So the macro should really behave like one.

    So we should have a macro needing semi-colon.

    Produce a valid code

    As shown in jfm3's answer, sometimes the macro contains more than one instruction. And if the macro is used inside a if statement, this will be problematic:

    if(bIsOk)
       MY_MACRO(42) ;
    

    This macro could be expanded as:

    #define MY_MACRO(x) f(x) ; g(x)
    
    if(bIsOk)
       f(42) ; g(42) ; // was MY_MACRO(42) ;
    

    The g function will be executed regardless of the value of bIsOk.

    This means that we must have to add a scope to the macro:

    #define MY_MACRO(x) { f(x) ; g(x) ; }
    
    if(bIsOk)
       { f(42) ; g(42) ; } ; // was MY_MACRO(42) ;
    

    Produce a valid code 2

    If the macro is something like:

    #define MY_MACRO(x) int i = x + 1 ; f(i) ;
    

    We could have another problem in the following code:

    void doSomething()
    {
        int i = 25 ;
        MY_MACRO(32) ;
    }
    

    Because it would expand as:

    void doSomething()
    {
        int i = 25 ;
        int i = 32 + 1 ; f(i) ; ; // was MY_MACRO(32) ;
    }
    

    This code won't compile, of course. So, again, the solution is using a scope:

    #define MY_MACRO(x) { int i = x + 1 ; f(i) ; }
    
    void doSomething()
    {
        int i = 25 ;
        { int i = 32 + 1 ; f(i) ; } ; // was MY_MACRO(32) ;
    }
    

    The code behaves correctly again.

    Combining semi-colon + scope effects?

    There is one C/C++ idiom that produces this effect: The do/while loop:

    do
    {
        // code
    }
    while(false) ;
    

    The do/while can create a scope, thus encapsulating the macro's code, and needs a semi-colon in the end, thus expanding into code needing one.

    The bonus?

    The C++ compiler will optimize away the do/while loop, as the fact its post-condition is false is known at compile time. This means that a macro like:

    #define MY_MACRO(x)                                  \
    do                                                   \
    {                                                    \
        const int i = x + 1 ;                            \
        f(i) ; g(i) ;                                    \
    }                                                    \
    while(false)
    
    void doSomething(bool bIsOk)
    {
       int i = 25 ;
    
       if(bIsOk)
          MY_MACRO(42) ;
    
       // Etc.
    }
    

    will expand correctly as

    void doSomething(bool bIsOk)
    {
       int i = 25 ;
    
       if(bIsOk)
          do
          {
             const int i = 42 + 1 ; // was MY_MACRO(42) ;
             f(i) ; g(i) ;
          }
          while(false) ;
    
       // Etc.
    }
    

    and is then compiled and optimized away as

    void doSomething(bool bIsOk)
    {
       int i = 25 ;
    
       if(bIsOk)
       {
          f(43) ; g(43) ;
       }
    
       // Etc.
    }
    

提交回复
热议问题