Stupid C++ Tricks: do/while(0) and C4127

March 1, 2009
Tags: , , , ,

A nice little quickie:  I briefly discuss in my assert ramblings why it’s important to wrap all of your multi-line macros in do/while(0) blocks.  An unfortunate side-effect of this is that the construction

// NOISY CODE
#define MULTI_LINE_MACRO \
	do { \
		std::printf("Hello "); \
		std::printf("world!\n"); \
	} while (0)

will trigger C4127: “Conditional expression is constant” in Visual Studio 2003/2005, and probably 2008 as well (but I haven’t tried it).  The culprit is the “while(0)” part, the compiler thinks we’re making a mistake when of course this is all very intentional. Let’s see what we can do to fix it!

This is really unfortunate because these macros live in header files, and for the life of me I couldn’t figure out a way to shut up that warning!  The best way I’d found to deal with it was simply to issue a sledgehammer #pragma “shut the hell up” directive:

// QUIET BUT RUDE CODE
#pragma warning(disable:4127)
#define MULTI_LINE_MACRO \
	do { \
		std::printf("Hello "); \
		std::printf("world!\n"); \
	} while (0)

This is very bad.  I don’t want to globally silence 4127, just only for my multi-line macro stuff, but now every cpp file that #includes my header file has 4127 forever silenced!  This is inconvenient enough inside your own codebase, but it’s flat-out rude if you’re writing middleware!  I’ve used middleware that changes the warning levels on my cpp files, and it was an unpleasant surprise to find that the warnings I was expecting to receive were being muted before the compiler ever saw any of my code!

What we really want to do is something like this:

// BROKEN CODE
#define MULTI_LINE_MACRO \
	do { \
		std::printf("Hello "); \
		std::printf("world!\n"); \
#pragma warning(push)
#pragma warning(disable:4127)
	} while (0)
#pragma warning(pop)

This preserves the global warning state, but still disables 4127 for the duration of our intentional violation. Unfortunately though, since the preprocessor isn’t recursive, that’s illegal.  We can’t put #pragma directives inside macro definitions!  We can also try doing something like this:

// STUPID CODE
#pragma warning(push)
#pragma warning(disable:4127)
#define MULTI_LINE_MACRO \
	do { \
		std::printf("Hello "); \
		std::printf("world!\n"); \
	} while (0)
#pragma warning(pop)

But that doesn’t do what we want at all, because of course the #pragmas don’t wrap the macro at its invocation site, and wrapping the definition doesn’t do anything!

Well, it turns out that Microsoft implemented the __pragma directive for just this purpose. Using __pragma instead of #pragma lets you embed pragma directives inside of macros!  This turns out to be just what the doctor ordered.  We can now throw together multi-line macro wrappers that emit no warnings on /W4 and don’t change the global warning state!

// WORKING CODE
#define MULTI_LINE_MACRO \
	do { \
		std::printf("Hello "); \
		std::printf("world!\n"); \
__pragma(warning(push)) \
__pragma(warning(disable:4127)) \
	} while (0) \
__pragma(warning(pop))

We can now generalize this approach to a set of helper macros that help you always correctly implement multi-line macros:

#define MULTI_LINE_MACRO_BEGIN do {
#define MULTI_LINE_MACRO_END \
	__pragma(warning(push)) \
	__pragma(warning(disable:4127)) \
	} while(0) \
	__pragma(warning(pop))

And here’s our original example reimplemented:

#define MULTI_LINE_MACRO \
	MULTI_LINE_MACRO_BEGIN \
		std::printf("Hello "); \
		std::printf("world!\n"); \
	MULTI_LINE_MACRO_END

Finally, I should point out that both a coworker of mine and someone on the boost mailing list have found another slightly bizarre workaround to this problem:  For some reason, the code

// WORKING CODE, BUT A LITTLE SCARY
#define MULTI_LINE_MACRO \
	do { \
		std::printf("Hello "); \
		std::printf("world!\n"); \
	} while (__LINE__ == -1)

 
doesn’t trigger C4127.  I can only conclude that it’s unintended compiler behavior, because replacing __LINE__ with a literal integer causes it to trigger, and I can’t imagine that MS intended that code to be the correct way to dodge 4127.  Use it at your own peril, and don’t be surprised if a new version of Visual Studio or some random service pack breaks it!

19 Responses to “Stupid C++ Tricks: do/while(0) and C4127”

  1. You had me at boost. I already hate it a little bit, the last trick that is :) But then again I’m so burned by boost it’s not even funny.

  2. Thanks for the tip chas! Going to go add this into my code now…

  3. I use something quite similar:

    namespace Private
    {
    inline bool False() { return false; }
    }

    #define ASSERT(expr) \
    do \
    { \
    …stuff…
    } \
    while (::MyNamespace::Private::False())

    It’s verbose, but harder to break and perhaps a little more intuitive.

  4. Overall good tip just one problem:

    Does not while(__LINE__ != -1) evaluate to while(true)?

    What is more likely to work is while(__LINE__ == -1).

  5. Thanks Vambola, good catch :) You can tell I didn’t copy-paste actual code when writing the article…

  6. Thanks a lot! A concise and well explained solution to a problem I was chasing all day long.

  7. This is a really great little article, thanks! I’m now using the following in my own code:

    #define ASSERT_FALSE ( __LINE__ == -1 )

  8. I found this site while googling and you have the same name for me and I also want to be a programmer, that shit is weird.

    Charles

  9. There can be only one.

  10. Another trick that’s worked for me is replacing the literal true/false with: (false, true) or (true, false) as appropriate.

  11. This old C trick can be done another way with C++ without warning nor needing any pragma and sticking with the standards. I don’t know if it has any performance impact.

    #define MULTI_LINE_MACRO \
    try { \
    std::printf(“Hello “); \
    std::printf(“world!\n”); \
    } catch (…) { throw; }\

  12. Christophe, I like that solution, but unfornately you can’t use that macro in a destructor without asking for serious trouble.

    Not that I like the do {} while(false) trick any better, mind you. :)

  13. Also, Christophe, it appears that macro wouldn’t eat a trailing semicolon.

  14. Uff, why not simply

    # #define MULTI_LINE_MACRO \
    { \
    std::printf(“Hello “); \
    std::printf(“world!\n”); \
    }

    ?

  15. Because your simpler example doesn’t work.

    #define MULTI() { std::printf(“Hello”); std::printf(” world!\n”); }

    fails when you do this:

    if (a)
    MULTI();
    else
    MULTI();

    The semicolons create syntax errors.

    [WORDPRESS HASHCASH] The poster sent us ’0 which is not a hashcash value.

  16. Well, you are right. It does not eat semicolon, Hence a try catch as I proposed is in no way better than a simple brackets pair. Too bad. Back to do {} while(0) trick… or back to inline functions if you use C++.

    I disagree with the comment about destructors. You can use try catch in destructors, the only thing that is calling for problems is throwing exceptions outside destructors. Even more, you **should** catch every exceptions possibly thrown by functions called in a destructor or it will go outside and **then** you will have problems…

  17. I have the following macro definitions that tells the compiler to shut-up about unused variables/parameter; which have recently been changed from the old stand-by:

    [code]
    #define UNUSED(var) (var)
    [/code]

    to:

    [code]
    //**********************************************************
    // Nifty little helper macros that are used *everywhere*
    //**********************************************************
    #define UNUSED(var) MACRO_BEGIN \
    (void)sizeof(var); \
    MACRO_END
    #define UNUSED_VARIABLE(var) UNUSED(var)
    #define UNUSED_PARAMETER(prm) UNUSED(prm)
    [/code]

    Now, I have tried virtually every alternative presented within this article and related comments except for the try/catch above to accomplish 3 things:

    1) Avoid the C4127 warning,
    2) Remove/reduce the required assembly code to shut-up the compiler, and;
    3) if possible, remove compiler dependancy from the code (ie: Microsoft VC++)

    Here’s what I found out…

    [code]
    #define MACRO_END } while(__LINE__ == -1)
    [/code]

    still gives me the C4127 warning – I am running VC++ 2008 w/sp1 – So I will continue to test other alternatives…

    [code]
    inline bool False(void) {return false; }
    #define MACRO_BEGIN do {
    #define MACRO_END } while(False())
    [/code]

    My first thoughts were that this would “optimize out” the *call* to False() and replace it with a simple MOV EAX, 0 – but this didn’t happen. Instead, I got the following generated assembly code…

    [code]
    $LN3@Update:

    ; 494 : UNUSED_PARAMETER(elapsed_time);

    01297 e8 00 00 00 00 call ?False@TOOLBOX@@YA_NXZ ; TOOLBOX::False
    0129c 0f b6 c0 movzx eax, al
    0129f 85 c0 test eax, eax
    012a1 75 f4 jne SHORT $LN3@Update

    [/code]

    Notice the call to False() is still there. Of course, it’s immediately followed by the mov instruction I was expecting (a variant anyway). I noticed that the move was dealing with al (1-byte) – duh… type bool is 1-byte so this makes sense… I then replaced the False() function with the following definition…

    [code]
    inline int False(void) { return 0; }
    [/code]

    and this time the generated code looks like:

    [code]
    $LN3@Update:

    ; 494 : UNUSED_PARAMETER(elapsed_time);

    01297 e8 00 00 00 00 call ?False@TOOLBOX@@YAHXZ ; TOOLBOX::False
    0129c 85 c0 test eax, eax
    0129e 75 f7 jne SHORT $LN3@Update
    [/code]

    Better, but notice the call to False() is still there… Maybe that has something to do with my compiler settings, but I don’t want to have to “tweak” the compiler for this one macro / function…

    So, I moved on to the next alternative…

    [code]
    #define MACRO_END \
    __pragma(warning(push)) \
    __pragma(warning(disable:4127)) \
    } while(0) \
    __pragma(warning(pop))
    [/code]

    This doesn’t even compile. I get “error C2017: illegal escape sequence” and related messages… In addition, __pragma() is Microsoft specific. I really don’t like this alternative anyway so on to the next…

    [code]
    #define MACRO_END } while(true,false)
    [/code]

    Now this is promising… This version generates the following assembly code and as far as I know, is not compiler dependant nor does it generate a C4127 warning…

    [code]
    $LN3@Update:

    ; 494 : UNUSED_PARAMETER(elapsed_time);

    01297 33 c0 xor eax, eax
    01299 75 fc jne SHORT $LN3@Update
    [/code]

    So, as a test, I set the MACRO_END definition back to the original:

    [code]
    #define MACRO_END } while(0)
    [/code]

    and accepting the C4127 warning, I wanted to see the generated code and with this I get the following assembly output:

    [code]
    $LN3@Update:

    ; 494 : UNUSED_PARAMETER(elapsed_time);

    01297 33 c0 xor eax, eax
    01299 75 fc jne SHORT $LN3@Update
    [/code]

    Which is identical to the version immediately above…

    Therefore, it seems to me that the method of defining MACRO_END as:

    [code]
    #define MACRO_END } while(true,false)
    [/code]

    satisfies my requirements. So in conclusion, I will be sticking to the while (true,false) variety as proposed by Phill Djonov above. Thanks Phill!

    NOTE: All of the above generated code was for x86 in RELEASE build. I have no idea of the generated Itanium or what-not processor’s code. Ideally, I’d like to remove the xor and jne instructions too, however, they appear to be a by-product of the do..while() loop. As arguments for their inclusion seems to be reasonable, I guess I will continue with this approach. Anyone know of a way to eliminate those?

    Jumpster

  18. I guess someone will point out to me why the following idea is no good:

    instead of…
    do { } while (FALSE)

    …I defined a global constant and modified the macro to…
    do { } while (myAlwaysFalseConstant)

    This doesn’t produce a warning (at least not in the environments I’ve tested: Visual Studio 2008 and TI’s Code Composer 3.3 for the 320C6400 DSP family) and doesn’t rely on undocumented compiler behavior. It does produce slightly more code than do { } while (FALSE), but very marginally so.

    For me, wishing to support both a Visual Studio environment for off-line testing and a DSP environment for target code, the do { } while (__LINE__ == -1) alternative is not an option.

    Of course, some people dislike globals as a matter of principle. When you tell me the idea is no good, I shall expect a better reason than that ;-)

  19. Regarding “__pragma” and “this won’t even compile”, I’m not sure what you’re talking about.

    http://msdn.microsoft.com/en-us/library/d9x1s805.aspx

    Details how it works, and it does work, successfully, on both VS2005 and VS2008. I didn’t care so much about other compilers than MSVC and gcc for this experiment, but since gcc doesn’t have an equivalent warning, it didn’t matter for me.

    I’m not sure why “while (true, false)” wouldn’t trigger it, but I wouldn’t be surprised to see it start issuing a warning as compilers get better…

Leave a Reply