C/C++:如何在没有编译器警告(如C4127)的情况下使用do-while(0)构造?

45

我经常在我的 #defines 中使用 do-while(0) 结构,理由在这个答案中有描述。此外,我试图使用尽可能高的编译器警告级别来捕获更多潜在问题,使我的代码更健壮、跨平台。因此,我通常使用 gcc 的 -Wall 和 MSVC 的 /Wall

不幸的是,MSVC 对于 do-while(0) 结构会发出警告:

foo.c(36) : warning C4127: conditional expression is constant

我应该如何处理这个警告?

全局禁用对所有文件来说好像不是一个好主意,那该怎么办呢?


1
既然您已经添加了C++标签,那么将#define宏转换为内联函数是否可行?这样会更安全。 - Thomas Matthews
我还添加了C标签,所以我认为我是在询问与C兼容的解决方案。那么我应该删除C++标签吗? - bialix
你尝试过像 sizeof(char) != 1 这样的条件吗? - Tony Delroy
@bialix:由于语言和解决方案差异巨大,是的。删除您不想要答案的语言标签。 - Mooing Duck
21个回答

46

概述:在这种情况下,C4127警告是一个微妙的编译器bug。可以随意禁用它。

详细信息:

C4127警告本来是用来捕获在不明显的情况下逻辑表达式求值为常量的情况(例如,if(a==a && a!=a)),但一些时候,它会导致while(true)等有用的结构被标记为无效。

Microsoft建议如果您想要启用此警告并且没有其他解决方案,则使用for(;;)来实现无限循环。这是我们公司开发约定中允许禁用的极少数4级警告之一。


对我来说,for(;;) 看起来很丑。 - Pavel Radzivilovsky
1
这是我们的做法,但我们需要手动在Boost头文件前后添加编译指示。 - Alexandre C.
我不会把那个警告称为编译器的错误。它按照预期和文件记录工作,并且有时候找到真正的错误。我更喜欢使用 __pragma 方案来全局关闭警告。 - Adrian McCarthy
如果 if (1) {stuff;} else 后面没有分号会怎么样呢?或者是 do { stuff; if (always_true_condition) break; } while(1) 呢?显然,带有内部 breakreturn 语句的 do...while(1) 循环以及带有恒定条件的 if 语句非常普遍。 - supercat
在VS2015中,可以轻松地从项目属性/属性表中禁用它 ->“常规属性-> C / C ++->高级->禁用特定警告”。在那里,您可以添加要禁用的警告,以分号分隔。 - mtb
显示剩余3条评论

28

也许你的代码需要更多的猫头鹰:

do { stuff(); } while (0,0)

或者不太好看但也会产生较少警告的:

do { stuff(); } while ((void)0,0)

1
有趣,但实际上并没有帮助。它产生了另一个警告 :-)警告 C4548:逗号前的表达式没有效果;预期具有副作用的表达式。 - bialix
8
可以这样简单地修复:do {} while((void)0, 0) - sharptooth
2
stuff() 后面不应该加分号吗? - Michel de Ruiter
1
第二个版本在代码分析VC2012+中也会生成C6319警告。 - Matthieu

18

正如Michael BurrCarl Smotricz答案中所指出的,对于Visual Studio 2008+,您可以使用__pragma

#define MYMACRO(f,g)              \
  __pragma(warning(push))         \
  __pragma(warning(disable:4127)) \
  do { f; g; } while (0)          \
  __pragma(warning(pop))

如果您更喜欢将宏定义写在一行上(没有\),那么您可以这样做,但这样可能会使得宏定义难以阅读。


3
这里的推动(push)太早了 - 如果你仍想捕捉到"f"和"g"语句中的问题,你需要立即在 while (0)之前发出警告推送(warning push)。参见其他MULTI_LINE_MACRO答案以获得某些模糊的可重用解决方案。 - Tom Whittock

15

我有一个基于这里的一个答案的模式,它可以在clang、gcc和MSVC上工作。我在这里发布它,希望它对他人有用,并因为这里的答案帮助了我构建它。

#ifdef WIN32
#  define ONCE __pragma( warning(push) ) \
               __pragma( warning(disable:4127) ) \
               while( 0 ) \
               __pragma( warning(pop) )
#else
#  define ONCE while( 0 )
#endif

我是这样使用它的:

do {
   // Some stuff
} ONCE;

您也可以在宏中使用此功能:

void SomeLogImpl( const char* filename, int line, ... );    

#ifdef NDEBUG
#  define LOG( ... )
#else
#  define LOG( ... ) do { \
      SomeLogImpl( __FILE__, __LINE__, __VA_ARGS__ ); \
   } ONCE
#endif

如果 F 在函数中使用 'ONCE',那么上面指出的情况也适用:

This also works for the case pointed out above, if F uses 'ONCE' in a function:
#define F( x ) do { f(x); } ONCE
...
if (a==b) F(bar); else someFunc();

编辑:多年之后,我意识到我忘记了添加这个宏实际上是为了哪个模式而编写的 - “switch-like-a-goto”模式:

do {
    begin_some_operation();

    if( something_is_wrong ) {
        break;
    }

    continue_big_operation();

    if( another_failure_cond ) {
        break;
    }

    finish_big_operation();
    return SUCCESS;
} ONCE;

cleanup_the_mess();
return FAILURE;

这提供了一种类似于try/finally结构的构造,比混乱的goto清理和返回代码更加有结构性。使用这个ONCE宏而不是while(0)可以让VS安静下来。


这是一个很好的表达方式。 - Joel Falcou

5
使用更高版本的MS编译器,您可以使用警告抑制:
#define MY_MACRO(stuff) \
    do { \
        stuff \
    __pragma(warning(suppress:4127)) \
    } while(0)

您也可以推送/禁用/弹出,但抑制是一种更加方便的机制。


2
更方便的错误解决机制,而不是修复错误。做得好,微软! - Slava

3

这里是另一种可能的方法,它避免了C4127、C4548和C6319(VS2013代码分析警告),不需要宏或编译指示:

static const struct {
    inline operator bool() const { return false; }
} false_value;

do {
    // ...
} while (false_value);

这段代码在GCC 4.9.2和VS2013中可以优化并且编译时不会出现警告。实际应用中,它可以放在一个命名空间中。


3
这个编译器错误已经在Visual Studio 2015 Update 1中修复,即使发行说明没有提到它。
尽管如此,在之前的答案中解释了这个错误:
总体概括:在这种情况下,警告(C4127)是一个微妙的编译器错误。可以自由禁用它。 它的目的是在逻辑表达式在非显而易见的情况下被评估为常量时(例如,如果(a == a && a!= a)),并且以某种方式将while(true)和其他有用的结构转换为无效状态时进行检测。

2

您可以使用

do {
    // Anything you like
} WHILE_FALSE;

之前定义了一个WHILE_FALSE宏,如下所示:

#define WHILE_FALSE \
    __pragma(warning(push))         \
    __pragma(warning(disable:4127)) \
    while(false)                    \
  __pragma(warning(pop))

已通过MSVC++2013验证。


2
警告是由于while(false)引起的。这个网站提供了一个解决此问题的例子。下面是来自该网站的示例(您需要为您的代码重新设计它):
#define MULTI_LINE_MACRO_BEGIN do {  
#define MULTI_LINE_MACRO_END \  
    __pragma(warning(push)) \  
    __pragma(warning(disable:4127)) \  
    } while(0) \  
    __pragma(warning(pop))

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

只需将您的代码插入BEGIN和END之间即可。


1
我发现这是最短的版本。
do {
  // ... 
}
while (([]() { return 0; })())  /* workaround for MSVC warning C4172 : conditional expression is constant */

我猜编译器已经优化掉了,但还没有检查。

它被优化掉了。 - bolov
你可以去掉一组括号:} while ([]() { return 0; }()); - bolov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接