【问题标题】:C/C++: How to use the do-while(0); construct without compiler warnings like C4127?C/C++: 如何使用 do-while(0);构造没有像 C4127 这样的编译器警告?
【发布时间】:2010-12-29 02:54:37
【问题描述】:

由于in this answer 所述的原因,我经常在我的#defines 中使用do-while(0) 构造。此外,我正在尝试使用编译器的尽可能高的警告级别来捕捉更多潜在问题,并使我的代码更加健壮和跨平台。所以我通常使用 -Wall 和 gcc 和 /Wall 和 MSVC。

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

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

我应该怎么处理这个警告?

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

【问题讨论】:

  • 既然你已经添加了 C++ 标签,是否可以将 #define 宏转换为内联函数?这样会更安全。
  • 我还添加了 C 标签,所以我想我已经询问了与 C 兼容的解决方案。那我应该删除 C++ 标签吗?
  • 你试过像 sizeof(char) != 1...这样的条件吗?
  • @bialix:因为语言和解决方案有很大不同,所以是的。删除您不想回答的语言标签

标签: c++ c visual-c++ macros compiler-warnings


【解决方案1】:

请使用编译器开关 /wd"4127" 在您的项目中禁用此警告。

【讨论】:

  • OP 专门开启了所有警告,为什么建议禁用呢?这将破坏首先启用警告的目的。
  • 这再次违背了最初打开它的目的。在 MSVC 中可能还有很多其他地方需要 OP 进行检查。
  • 我建议仅对 msvc 禁用此警告。他仍然可以使用 gcc 获得所有警告。每个编译器都有不同的警告报告策略。 msvc 对于某些类型的警告是严格的,然后是 gcc。知道不需要 msvc 的建议,您可以禁用该警告。对我来说,在所有编译器中启用所有警告,然后为该特定编译器禁用其中一些过于严格或错误报告的警告更有益。
  • @ChrisMM 什么无法理解没有灵丹妙药这样的东西。每个解决方案都适用于某些特定情况。您需要查看已接受的答案,并鼓励您禁用此警告。
  • 在打开所有警告后禁用警告会破坏打开所有警告的目的。我不确定这有什么复杂的。我知道您是在说“仅在 ​​MSVC 中”,但如果那是主要的开发环境呢?也许OP正在远离gcc? OP 特别声明 只是为所有文件全局禁用它?对我来说这似乎不是一个好主意,它清楚地表明您提出的解决方案对他们不利。而且,正如您所提到的,其他答案已经建议禁用,那么在这一点上,您的答案添加了什么?
【解决方案2】:

您可以将for 循环用作:

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

宏:

#define BEGIN \
  for (;;) {

#define END \
  break; }

【讨论】:

    【解决方案3】:

    你可以使用

    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 上验证。

    【讨论】:

      【解决方案4】:

      我有一个基于此处答案的模式,它适用于 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':

      #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-ish 结构,该结构比您的清理和返回代码的笨拙 goto 更有条理。使用这个 ONCE 宏而不是 while(0) 会关闭 VS。

      【讨论】:

      • 这是一种很好的表达方式。
      【解决方案5】:

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

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

      这会优化,并且在 GCC 4.9.2 和 VS2013 中编译时不会出现警告。在实践中,它可以放在命名空间中。

      【讨论】:

        【解决方案6】:

        此编译器错误已在 Visual Studio 2015 Update 1 中修复,即使 releases notes 没有提及。

        虽然在之前的答案之一中解释了该错误:

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

        它旨在捕捉逻辑表达式在非显而易见的情况下评估为常量的情况(例如,if(a==a && a!=a),不知何故,它变成了 while(true) 和其他有用的构造成无效的。

        【讨论】:

          【解决方案7】:

          我发现这是最短的版本

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

          还没有检查它是否被编译器优化掉了,但我猜是的。

          【讨论】:

          • 它被优化掉了。
          • 可以去掉一组括号:} while ([]() { return 0; }());
          【解决方案8】:

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

          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
          

          【讨论】:

            【解决方案9】:

            嗯,对我来说,以下工作没有 C4127 警告:

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

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

            【讨论】:

              【解决方案10】:

              正如Michael Burr 中提到的Carl Smotricz'answer,对于Visual Studio 2008+,您可以使用__pragma:

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

              如果您希望宏不可读,可以将其放在一行(不带 \s)。

              【讨论】:

              • 这里的推送太早了——如果你仍然想在f和g语句中发现问题,你需要在while (0)之前立即进行警告推送。请参阅其他 MULTI_LINE_MACRO 答案以了解可重复使用的内容。
              【解决方案11】:

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

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

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

              【讨论】:

              • 更方便的错误解决机制,而不是修复错误。干得好 MS!
              【解决方案12】:

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

              深入:

              它旨在捕捉逻辑表达式在非显而易见的情况下评估为常量的情况(例如,if(a==a && a!=a),不知何故,它使while(true) 和其他有用的构造无效。

              如果您想打开此警告,Microsoft 建议使用 for(;;) 进行无限循环,并且您的情况没有解决方案。这是我公司的开发约定允许禁用的极少数 4 级警告之一。

              【讨论】:

              • 我知道 for(;;) 但正如你所说,它不适用。我倾向于听从你的建议,只是等待一些其他的建议。
              • 对我来说,for(;;) 看起来很丑。
              • 在我的公司也被禁用(实际上是在每个标题的基础上)。这会在使用 boost 时破坏构建输出。
              • @alexandre 使用 boost 时,您可以在标题包含周围禁用它。 #include "warningsoff.h",然后是 "warningson.h" 以使其恢复到所需的水平。
              • 这就是我们所做的,但是我们手动将 pragma 放在 boost headers 之前和之后。
              【解决方案13】:

              对于要在表达式中使用的多语句宏,您可以使用逗号运算符而不是 do-while(0) 构造。所以而不是:

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

              用途:

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

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

              【讨论】:

                【解决方案14】:

                也许你的代码需要more owls:

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

                或者更不上镜但也更少产生警告:

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

                【讨论】:

                • 有趣,但实际上并没有帮助。它产生另一个警告 :-) 警告 C4548:逗号前的表达式无效;具有副作用的预期表达式
                • @bialix:可以像这样轻松修复:do {} while( (void)0, 0)
                • stuff()后面不应该有分号吗?
                • 第二个版本在代码分析VC2012+中也会产生C6319警告。
                【解决方案15】:

                我会用

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

                这相当于

                do
                {
                }while(0);
                

                并且不会产生任何警告。

                【讨论】:

                • 你没抓住重点,do { } while(0)注意分号的缺少,用于宏for a reason
                【解决方案16】:

                警告是由于while(false)。这个site 给出了一个如何解决这个问题的例子。来自网站的示例(您必须为您的代码重新编写它):

                #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 之间插入您的代码。

                【讨论】:

                  【解决方案17】:

                  您可以使用#pragma 警告来:

                  1. 保存状态
                  2. 禁用警告
                  3. 编写有问题的代码
                  4. 将警告恢复到之前的状态

                  (您需要在 pragma 之前添加一个 #,但 SO 很难同时处理它们并进行格式化)

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

                  您想要推送/弹出警告而不是禁用/启用,因为您不想干扰可能选择打开/关闭警告的命令行参数(有人可能会使用命令行来关闭警告,你不想强行重新启动它......上面的代码处理)。

                  这比全局关闭警告要好,因为您可以仅针对您想要的部分进行控制。你也可以让它成为宏的一部分。

                  【讨论】:

                  • 我不确定如何将其作为宏的一部分。如果我在 #define foo(x) \ 后面加上编译指示,我会得到错误:错误 C2162:预期的宏形式参数
                  • 我手边没有 MS 编译器...所以无法测试。如果你不能把它放在宏中,我猜想把宏的用法包装在那个代码中。你确定你是作为#pragma 做的吗? (只是检查:-)
                  • 您不能将编译指示放入宏中,因此您必须在使用宏的任何地方禁用警告。讨厌。
                  • Bialix:将宏扩展为对内联函数的调用。将 do-while 放在这个函数中并用 pragma 包装它。
                  【解决方案18】:

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

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

                  【讨论】:

                  • 我几乎不希望有人使用 MSVC 来编译纯 C。
                  • 好的,那我就是那个疯子。顺便说一句,Python 的 C 扩展通常在 Windows 上使用 MSVC 编译。
                  • +1 如果您正在编译 C++,第一个宏很好,因为它是可移植的,而且它不会对构建环境进行全局更改。第二个问题是 do {} while (false) 解决方案旨在解决的悬空 else 问题。
                  【解决方案19】:

                  有一个解决方案,但它会为您的代码添加更多循环。不要在 while 条件中使用显式值。

                  你可以这样:

                  file1.h

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

                  变量 I_am_a_zero 应该在某个 .c 文件中定义。

                  无论如何,这个警告不会出现在 GCC 中:)

                  看到这个related question。

                  【讨论】:

                  • 这个 const 变量不是会阻止优化循环并引入不必要的零检查吗?
                  • 是的,这就是 Yousf 说它将为您的代码添加更多循环的原因。
                  • 不,编译器在编译时不会知道变量“I_am_a_zero”实际上是零;因为它是一个外部符号,将在链接阶段实现。
                  【解决方案20】:

                  我必须说,我从来没有打扰过宏中的 do..while 构造。我的宏中的所有代码本身都包含在大括号中,但没有 do-..while。例如:

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

                  此外,我自己的编码标准(以及多年来的非正式实践)一直是让所有块,无论它们出现在哪里,都用大括号括起来,这也或多或少地解决了这个问题。

                  【讨论】:

                  • do-while(0) 是广泛使用的构造 AFAIK。
                  • 我知道。我的建议是没有必要。
                  • +1 它可能被“广泛”使用(无论如何,我从未在生产代码中见过它)但我认为没有必要。毕竟首先应该避免使用所有宏,所以如果你把它们放进去,保持它们简单。不要在我的代码中引入“循环”。
                  • @Tomas 见我的第二段。
                  • @Neil 你的编码习惯不能适用于所有人。所以我认为你的回答不是一个好的指南。一些带有分号的行和一些没有分号的行是可怕的。
                  【解决方案21】:

                  这个“while(0)”的东西是个骗子,刚转身就咬你。

                  您的编译器是否提供#pragmas 以选择性地在本地关闭特定的错误消息?如果是这样,那可能是一个明智的选择。

                  【讨论】:

                  • 是的,MSVC 支持编译指示,但我如何将宏本身包装在编译指示中?
                  • 对不起,不知道。这是一个想法,来自我的提示,我希望它能激发解决方案。我不记得以前处理过这种问题。
                  • C99 将 _Pragma() 描述为预处理器形式 #pragma 的替代品,因此它可以作为宏的一部分生成。微软已经声明他们对实现 C99 不感兴趣,但我不知道这个功能是否已经存在(考虑到他们的前端供应商)。
                  • MSVC 有 __pragma() 预处理运算符,不幸的是它与 C99 的 _Pragma() 运算符略有不同(C99 采用字符串文字,MSVC 采用不在字符串中的标记):msdn.microsoft.com/en-us/library/d9x1s805.aspx
                  猜你喜欢
                  • 1970-01-01
                  • 2023-01-24
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多