【问题标题】:Is #defining within a block really a code smell?块内的#defining 真的是代码异味吗?
【发布时间】:2018-05-17 15:12:44
【问题描述】:

我读到块内的#defining 和#undefining 是代码异味。这是为什么?另外,我在代码中看到(只是一个示例,显示了我在实际代码中拥有的内容),这可以编译,

if(x == 1000) {
    #define MACRO_EXAMPLE 1
} else {
    #define MACRO_EXAMPLE 1
}

但这不会编译,

if(x == 1000) {
    #define MACRO_EXAMPLE 1
} else {
    #define MACRO_EXAMPLE 2
}

出现错误warning C4005: 'MACRO_EXAMPLE' : macro redefinition

如何进行预处理?它是如何工作的?

【问题讨论】:

  • 您可以在编译时使用ififdef 指令来定义特定的宏,这本身并不是不好的做法。
  • 这不是代码异味,它只是不起作用。
  • 预处理器指令不注意除以“#”开头的行之外的任何内容。
  • 有些人可能不同意,但实际上在大多数情况下我会反对他们。宏有其用处,但全局定义很快就会失控,尤其是 constexpr 正在慢慢添加到事物中。
  • 诸如此类的预处理器指令一直用于嵌入式软件开发。我认为上下文对这个问题很重要。我们在谈论什么样的平台?什么样的代码:低级驱动程序,还是高级接口的东西?即使在其中,宏的使用也取决于情况和用例。

标签: c++ c macros preprocessor


【解决方案1】:

预处理发生在您的代码被分析之前。这意味着 if/else 被忽略并且

if(x == 1000) {
    #define MACRO_EXAMPLE 1
} else {
    #define MACRO_EXAMPLE 1
}

实际上被视为

#define MACRO_EXAMPLE 1
#define MACRO_EXAMPLE 1

这没关系,因为您使用相同的值。另一方面

if(x == 1000) {
    #define MACRO_EXAMPLE 1
} else {
    #define MACRO_EXAMPLE 2
}

变成

#define MACRO_EXAMPLE 1
#define MACRO_EXAMPLE 2

而且由于宏的值已经改变,这是一个错误

【讨论】:

    【解决方案2】:

    预处理器指令不关心块作用域,无论如何它们在编译时都会产生影响。您的代码示例

    if(x == 1000) {
        #define MACRO_EXAMPLE 1
    } else {
        #define MACRO_EXAMPLE 2
    }
    

    100%等同于写作

        #define MACRO_EXAMPLE 1
        #define MACRO_EXAMPLE 2
    if(x == 1000) {
    } else {
    }
    

    (调试信息中的行号除外)。现在应该更清楚为什么这是一个错误。这也是为什么在块内定义是一种代码味道——无论如何它们都有文件范围的影响。

    (我依稀记得有一个建议只在 C++ 中进行更改,但我认为它没有成功。)

    最好的方式是定义一个宏,只有一个顶级构造在该构造之前使用任何业务,然后立即再次取消定义它。你会看到X-macros,例如:

    #define X(a, b, c) b,
    const int b_tbl[] = {
        #include "tbl.inc"
    };
    #undef X
    

    但请注意,#define 和 #undef b_tbl 的定义之外,因此它们仍然作为文件范围读取给人类。

    【讨论】:

      【解决方案3】:

      处理#define 之类的东西的预处理器在程序的其余部分被解析之前在概念上运行。除其他外,这意味着预处理器定义不支持块范围。如果您在 { ... } 块内完成了 #define ,编译器不会注意到。没有机制可以在 { ... } 块的末尾自动执行 #undef

      如果您愿意,您可以手动执行此操作:您可以在块内使用#define,并记住在块末尾使用您自己的显式#undef。但这一切都取决于你:编译器不会检查你的工作,或者如果你弄错了就会警告你。

      因此,它被认为是不好的做法。 (我想这就是你所说的“代码异味”的意思。)这是不好的做法,因为它容易出错,而且没有好的、自动的方法来捕获任何错误。

      如果你使用预处理器#define,它应该是全局的(因为基本上,它全局的,不管你是否想要它)。

      当然,全局变量也不受欢迎。这与一个事实有关,现在,预处理器#define 几乎不被接受。

      像所有风格问题一样,这个问题可能有些争议。当我说手工制作的块范围预处理器定义是“糟糕的风格”时,这并不意味着对它们有一个铁定的规则。如果您愿意,并且您知道自己在做什么,则可以摆脱它们;这里没有人能阻止你。 (如果您在工作中编写代码,并且根据您公司的风格指南,您可能会在代码审查中受到责骂。)

      碰巧的是,我在工作中每天使用的代码库都充满了这些“本地范围”预处理器定义,因为我猜我的一些前辈认为它们很漂亮。我不喜欢它们,但它们确实有效并且不会造成问题,因此我们还没有开展消除它们的运动。

      【讨论】:

        【解决方案4】:

        宏由预处理器处理,该预处理器在实际编译之前运行。预处理器将文件视为纯文本,具有预处理器逻辑,不同于源代码逻辑。 使用 undef

        #undef MACRO_EXAMPLE
        

        这将取消定义,以便您稍后重新定义。它样式很差,但可以工作

        if(x == 1000) {
            #define MACRO_EXAMPLE 1
            //some code using the macro. Here it is expanded as 1
            #undef MACRO_EXAMPLE
        } else {
            #define MACRO_EXAMPLE 2
            //some code using the macro. Here it is expanded as 2
            #undef MACRO_EXAMPLE
        }
        

        【讨论】:

        • 预处理器指令不受 if 语句控制,所以你不能简单地取消定义宏。
        • 真的吗?它们是逐行的,因此在代码区域内宏具有正确的扩展。我以前做过。
        • 这段代码没有做 OP 想要的,但它确实避免了 preproc 重新定义错误消息
        • 您可以在任何地方定义/取消定义。所以代码是有效的!预处理器没有考虑块范围不是问题!这仍然是不好的做法,因为几乎不需要宏......现在可以在定义区域内使用宏。没有意义,但有效;)
        • 我添加的只是一个//评论。
        【解决方案5】:

        如何进行预处理?它是如何工作的?

        if-else 行不影响预处理器。

        就预处理器而言,这些行可能不存在。

        处理器只关心

            #define MACRO_EXAMPLE 1
            #define MACRO_EXAMPLE 1
        

        在第一种情况下

            #define MACRO_EXAMPLE 1
            #define MACRO_EXAMPLE 2
        

        第二种情况。

        【讨论】:

          猜你喜欢
          • 2016-11-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-02-04
          • 1970-01-01
          • 1970-01-01
          • 2018-11-23
          • 2011-05-30
          相关资源
          最近更新 更多