【问题标题】:Why no warning with "#if X" when X undefined?当 X 未定义时,为什么没有“#if X”警告?
【发布时间】:2010-12-11 06:11:30
【问题描述】:

我偶尔会写这样的代码:

// file1.cpp
#define DO_THIS 1

#if DO_THIS
    // stuff
#endif

在代码开发过程中,我可能会在 0 和 1 之间切换DO_THIS 的定义。

最近我不得不重新排列我的源代码并将一些代码从一个文件复制到另一个文件。但是我发现我犯了一个错误,两个部分像这样分开了:

// file1.cpp
#define DO_THIS 1

// file2.cpp
#if DO_THIS
    // stuff
#endif

显然我修复了错误,但后来我想,为什么编译器没有警告我?我将警告级别设置为 4。为什么在未定义 X 时#if X 不可疑?

还有一个问题:是否有任何系统的方法可以找出我是否在其他地方犯了同样的错误?这个项目很大。

编辑:我可以理解#ifdef 没有任何警告,这是非常有意义的。但肯定 #if 是不同的。

【问题讨论】:

  • 请记住,这是已定义的行为(未定义的预处理符号的值为 0),并且多平台程序很可能会提到许多未定义的预处理器宏。跨度>
  • 好问题,感谢发布。
  • 非常有趣的问题,但即使在 2020 年,问题标题中似乎也没有答案回答“为什么”。我访问这个页面是为了知道“为什么”(即标准委员会的初衷)......

标签: c++ visual-studio-2008 c-preprocessor


【解决方案1】:

gcc 可以为此生成警告,但标准可能不需要:

-Wundef
如果在 `#if' 指令中评估未定义的标识符,则发出警告。

【讨论】:

  • 很高兴看到 GNU 人员意识到这是一个潜在的错误。
  • 这也是安全关键编码标准中的通用规则。
【解决方案2】:

同样,正如经常发生的那样,“为什么”问题的答案只是:它是这样做的,因为前段时间它决定这样做。当您在 #if 中使用未定义的宏时,它将被 0 替换。您想知道它是否实际定义 - 使用 defined() 指令。

不过,“默认为 0”的方法有一些有趣的好处。尤其是当您使用可能由平台定义的宏,而不是您自己的宏时。

例如,某些平台提供宏 __BYTE_ORDER__LITTLE_ENDIAN__BIG_ENDIAN 来确定它们的字节顺序。您可以编写预处理器指令,如

#if __BYTE_ORDER == __LITTLE_ENDIAN
  /* whatever */
#else
  /* whatever */
#endif

但是如果你尝试在一个根本没有定义这些非标准宏的平台上编译这段代码(即对它们一无所知),上面的代码将被预处理器翻译成

#if 0 == 0
...

并且代码的小端版本将“默认”编译。如果你把原来的#if写成

#if __BYTE_ORDER == __BIG_ENDIAN
...

那么大端版本的代码将“默认”编译。

我不能说 #if 是专门针对上述技巧而定义的,但它有时会派上用场。

【讨论】:

  • “使用定义的()指令”...定义()?我在 msdn 上找不到。
  • 我说的是#if defined(<macro>)。在 MSDN 中,它应该在 #if 下。也许它被恰当地称为“操作员”,而不是“指令”。
  • @Bill:我的意思是特别是运营商defined(...)#if defined(...) 有一个等效的较短形式 #ifdef,但如果您需要一个更复杂的带有逻辑运算符的控制表达式,那么 defined(...) 是要走的路。
【解决方案3】:

当您无法使用带有警告消息的编译器(如 gcc 中的 -Wundef)时,我发现了一种产生编译器错误的有用方法。

你当然可以一直写:

#ifndef DO_THIS
    error
#endif
#if DO_THIS

但那真的很烦人

一个稍微不那么烦人的方法是:

#if (1/defined(DO_THIS) && DO_THIS)

如果 DO_THIS 未定义,这将产生除以零错误。这种方法并不理想,因为标识符被拼写了两次,第二次拼写错误会使我们回到开始的地方。看起来也很奇怪。似乎应该有一种更清洁的方法来实现这一点,例如:

#define PREDEFINED(x) ((1/defined(x)) * x)
#if PREDEFINED(DO_THIS)

但这实际上不起作用。

【讨论】:

  • 郑重声明,我在#define PREDEFINED(opt) (1 / defined opt## && opt) 方面取得了一些成功。连接是为了防止扩展,但老实说,我不明白为什么定义运算符周围缺少括号会影响评估。
【解决方案4】:

如果您急于防止此类错误,请尝试以下使用预处理器标记粘贴魔术和表达式评估来强制定义宏(在本例中为 0):

#define DEFINED_VALUE(x,y) (defined (y##x) ? x : 1/x)
#if DEFINED_VALUE(FEATURE1,) == 0

【讨论】:

    【解决方案5】:

    存在递归问题。如果你有

    #define MODEL  MODEL_A
    
    #if (MODEL == MODEL_B)
     // Surprise, this is compiled!
    #endif
    

    如果缺少 MODEL_A 和 MODEL_B 的定义,则会编译。

    #ifdef MODEL
    #error Sorry, MODEL Not Defined
    // Surprise, this error is never reached (MODEL was defined by undefined symbol!)
    #endif 
    
    #ifdef MODEL_B
    #error Sorry, MODEL_B Not Defined
    // This error is reached
    #endif 
    

    【讨论】:

      【解决方案6】:

      如果 DO_THIS 是您的定义,那么简单且有效的解决方案似乎是使用类似函数的宏:

      #define DO_THIS() 1
      
      #if DO_THIS()
          //stuff
      #endif
      

      我在 Visual Studio 2008、2015 和 GCC v7.1.1 下对此进行了测试。 如果 DO_THIS() 未定义 VS gererates:

      警告 C4067:预处理器指令后出现意外标记 - 需要换行符

      GCC 生成

      错误:在标记“(”之前缺少二元运算符

      【讨论】:

      • 那是因为括号。
      【解决方案7】:

      编译器没有生成警告,因为这是一个预处理器指令。它在编译器看到它之前被评估和解析。

      【讨论】:

      • 这是一个幽默的评论 - 但作为一个答案并不是很有帮助! 99.9% 的情况下,人们会说编译器,意思是从预处理到链接的所有内容……当您认为一次调用像“g++”这样的东西就可以完成所有事情时,这是合理的!
      • @Richard:这个答案实际上有点开玩笑。我很聪明地知道我经常看到技术上正确但完全没有价值的答案(在整个 C++ 社区中)。 ;) 如果我真的想把这个玩笑带到第 n 级,我也会包含一些对 C++ 标准的引用。
      【解决方案8】:

      如果我的想法正确的话。

      在编译任何源代码之前处理预处理器指令。在发生这种情况的翻译阶段,所有预处理器指令、宏等都会被处理,然后编译实际的源代码。

      由于#if 用于确定X 是否已定义,并在已定义或未定义时执行某些操作。代码 sn-p 中的#if 将编译没有任何错误,因为就编译器而言没有任何错误。您始终可以使用应用程序需要的特定#defines 创建一个头文件,然后包含该头文件。

      【讨论】:

        猜你喜欢
        • 2020-05-03
        • 1970-01-01
        • 2021-07-11
        • 1970-01-01
        • 2012-02-10
        • 2021-03-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多