C / C ++:如何使用do-while(0); 没有编译器警告像C4127构造?

我经常在我的#defines中使用do-while(0)构造,出于在这个答案中描述的原因。 另外我试图尽可能使用编译器提供的警告级别来捕捉更多的潜在问题,并使我的代码更加健壮和跨平台。 所以我通常使用-Wall与gcc和/Wall与MSVC。

不幸的是MSVC抱怨do-while(0)构造:

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

我应该怎么做这个警告?

只需全局禁用所有文件? 对我来说这似乎不是个好主意。

总结:在这种特殊情况下的这个警告(C4127)是一个微妙的编译器错误。 随意禁用它。

深入:

这是为了在非显而易见的情况下(例如, if(a==a && a!=a)逻辑expression式求值为一个常量,并且以某种方式将while(true)和其他有用的构造变成无效。

如果您希望发生此警告,Microsoftbuild议使用for(;;)进行无限循环,并且您的情况没有解决scheme。 这是我公司的开发惯例允许禁用的极less数Level-4警告之一。

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

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

或者不那么上镜,也不太警觉生产:

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

正如Michael Burr在Carl Smotricz的回答中指出的,对于Visual Studio 2008+,您可以使用__pragma :

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

你可以把它放在一行(没有\ s),如果你喜欢macros是不可读的。

我有一个模式,我基于这里的答案,它可以在叮,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; 

你也可以在macros中使用它:

 void SomeLogImpl( const char* filename, int line, ... ); #ifdef NDEBUG # define LOG( ... ) #else # define LOG( ... ) do { \ SomeLogImpl( __FILE__, __LINE__, __VA_ARGS__ ); \ } ONCE #endif 

这也适用于上面指出的情况,如果F在函数中使用“ONCE”:

 #define F( x ) do { f(x); } ONCE ... if (a==b) F(bar); else someFunc(); 

编辑:多年后,我意识到我忘了添加模式,我实际上写了这个macros – “开关像一个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-ish结构,它的结构比清理和返回代码的结构更加结构化。 使用这个ONCEmacros代替while(0)closuresVS。

使用较新版本的MS编译器,可以使用警告抑制:

 #define MY_MACRO(stuff) \ do { \ stuff \ __pragma(warning(suppress:4127)) \ } while(0) 

你也可以推/禁止/popup,但抑制是一个更方便的机制。

下面是另一种可能的方法,它避免了C4127,C4548和C6319(VS2013代码分析警告),并且不需要macros或杂注:

 static const struct { inline operator bool() const { return false; } } false_value; do { // ... } while (false_value); 

这优化了,并在GCC 4.9.2和VS2013没有警告编译。 实际上它可以进入命名空间。

警告是由于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之间插入代码。

这个编译器错误在Visual Studio 2015 Update 1中得到修复,即使版本说明没有提及它。

虽然之前的答案解释了这个错误:

总结:在这种特殊情况下的这个警告(C4127)是一个微妙的编译器错误。 随意禁用它。

这是为了在非显而易见的情况下(例如,如果(a == a && a!= a))将逻辑expression式求值为一个常量,并且在某种程度上将(true)和其他有用的构造变成无效。

您可以使用

 do { // Anything you like } WHILE_FALSE; 

而之前定义WHILE_FALSEmacros如下:

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

在MSVC ++ 2013上进行了validation。

这个“while(0)”的东西是一个黑客,刚刚转过身来咬你。

你的编译器是否提供#pragmaselect性地和本地地closures特定的错误信息? 如果是这样,那可能是一个明智的select。

#define STUFF for (bool b = true; b;) do {f(); g(); b = false;} while (b) #define STUFF for (bool b = true; b;) do {f(); g(); b = false;} while (b)

#define STUFF for (;;) {f(); g(); break;} #define STUFF for (;;) {f(); g(); break;}

您可以使用逗号运算符而不是do-while(0)构造多语句macros用于expression式中。 所以,而不是:

 #define FOO(...) do { Statement1; Statement2; Statement3; } while(0) 

使用:

 #define FOO(...) (Statement1, Statement2, Statement3) 

这与平台独立工作,并允许避免编译器警告(即使select了最高警告级别)。 请注意,在包含macros(第二个FOO)的逗号中,最后一条语句(Statement3)的结果将是整个macros的结果。

我必须说,我从来没有打扰过macros。 我的macros中的所有代码本身都包含在大括号中,但是没有这些。 例如:

 #define F(x) \ { \ x++; \ } \ int main() { int a = 1; F(a); printf( "%d\n", a ); } 

而且,我自己的编码标准(以及多年来的非正式练习)一直是要把所有的块都封装在大括号中,这或多或less地消除了这个问题。

有一个解决scheme,但它会增加更多的周期到你的代码。 在while条件中不要使用显式值。

你可以这样做:

file1.h

 extern const int I_am_a_zero; #define MY_MACRO(foo,bar) \ do \ { \ } \ while(I_am_a_zero); 

应该在一些.c文件中定义variablesI_am_a_zero。

无论如何,这个警告不会出现在GCC 🙂

看到这个相关的问题 。

您可以使用#pragma警告来:

  1. 保存状态
  2. 禁用警告
  3. 写出有问题的代码
  4. 将警告返回到以前的状态

(在编译前,你需要一个#,但是很难同时处理它们和格式化)

 #pragma warning( push ) #pragma warning( disable: 4127 ) // Your code #pragma warning( pop ) 

您想推/popup警告,而不是禁用/启用,因为您不想干扰可能select打开/closures警告的命令行参数(有人可能使用命令行closures警告,您可以不想强迫它…上面的代码处理)。

这比把全局警告closures好,因为你可以控制它只是为了你想要的部分。 你也可以使它成为macros的一部分。

那么,对我来说,没有C4127警告的下列作品:

 #define ALWAYS_TRUE(zzsome) ((##zzsome)==(##zzsome)) void foo() { int a = 0; while( ALWAYS_TRUE(a) ) { } } 

当然,编译器很聪明,zzsome不应该是一个常量

这将禁用警告,编译器将仍然能够优化代码:

 static inline bool to_bool(const bool v) { return v; } if (to_bool(0)) { // no warning here dead_code(); // will be compiled out (by most compilers) } do { something(); } while(to_bool(0)); // no extra code generated 

我发现这是最短的版本

 do { // ... } while (([]() { return 0; })()) /* workaround for MSVC warning C4172 : conditional expression is constant */ 

没有检查过,看它是否被编译器优化了,但我猜测它是。

你可以使用for循环:

 for (;;) { // code break; } 

macros:

 #define BEGIN \ for (;;) { #define END \ break; } 

我会用

 for(int i = 0; i < 1; ++i) //do once { } 

这相当于

 do { }while(0); 

并不会产生警告。