使用预处理器指令或if(constant)语句是否更好?


10

假设我们有一个代码库,可用于许多不同的客户,并且其中包含一些仅与类型X的客户相关的代码。最好使用预处理器指令将代码仅包含在类型X的客户中,或者使用if语句?要更清楚:

// some code
#if TYPE_X_COSTUMER  = 1
// do some things
#endif
// rest of the code

要么

if(TYPE_X_COSTUMER) {
    // do some things
}

我可以考虑的参数是:

  • 预处理程序指令可减少代码占用量并减少分支(在非优化编译器上)
  • 如果语句的结果是始终编译的代码,例如,如果某人犯了一个错误,而该错误将损害与其从事的项目无关的代码,则该错误仍然会出现,并且他不会破坏代码库。否则,他将不会意识到腐败。
  • 经常有人告诉我,比起预处理器,我更喜欢使用处理器(如果这完全是一个论点……)

什么是最好的-在谈论针对许多不同客户的代码库时?


3
您将交付未使用优化编译器创建的构建的可能性是多少?而且,如果发生这种情况,影响会很重要(尤其是当与您可能会错过的所有其他优化结合使用时)?

@delnan-该代码用于具有许多不同编译器的许多不同平台。关于影响-在某些情况下可能会。
MByD

有人告诉您您喜欢什么?这就像强迫某人不肯吃鸡肉而从肯德基吃东西。

@WTP-不,我不是,我想了解更多,所以我将来会做出更好的决定。
MByD 2011年

在您的第二个示例中,TYPE_X_CUSTOMER仍然是预处理程序宏吗?
熟练地

Answers:


5

我认为使用您没有提到的#define有一个优点,那就是您可以在命令行上设置值(因此,可以从一步构建脚本中设置它)。

除此之外,通常最好避免使用宏。他们不尊重任何范围界定,这可能会导致问题。只有非常笨拙的编译器无法基于编译时常数来优化条件。我不知道这是否与您的产品有关(例如,使代码在嵌入式平台上保持较小状态可能很重要)。


其实,这针对嵌入式系统
MByD

问题是嵌入式系统通常使用一些古老的编译器(例如gcc 2.95)。
2011年

@VJo-GCC是个幸运的案例:)
MByD 2011年

然后尝试一下。如果它包含未使用的代码并且大小相当大,则请使用定义。
陶Szelei

如果您希望能够通过编译器命令行进行设置,则我仍将在代码本身中使用常量,并且仅#define分配给该常量的值。
CodesInChaos 2014年

12

对于许多问题,这个问题的答案取决于。与其说这是更好的我已经相当给出的例子和目标,其中一个比其他更好。

预处理程序和常量都有其自己的适当用法位置。

如果是预处理器,则在编译之前删除代码。因此,它最适合预期不编译代码的情况。这可能会影响模块的结构,依赖性,并且可能允许针对性能方面选择最佳的代码段。在以下情况下,必须仅使用预处理程序来划分代码。

  1. 多平台代码:
    例如,当代码在不同平台上编译时,当代码依赖于特定的OS版本号(甚至是编译器版本-尽管这种情况很少见)时。例如,当您在处理低端代码和大端代码时,它们必须与预处理程序而不是常量分开。或者,如果您要同时为Windows和Linux编译代码,则某些系统调用会大不相同。

  2. 实验性补丁程序:
    这样做的另一种情况是证明其合理性是某些实验性代码存在风险,或者某些主要模块需要省略,这将导致明显的链接或性能差异。之所以要通过预处理器禁用代码而不是将其隐藏在if()下,是因为我们可能不确定此特定更改集引入的错误,并且我们正在实验的基础上运行。如果失败,除了重写,我们在生产中除了禁用该代码外别无选择。一段时间以来,最好使用#if 0 注释掉整个代码。

  3. 处理依赖关系:
    您可能希望生成的另一个原因例如,如果您不想支持JPEG图像,则可以帮助您摆脱对该模块/存根的编译,最终库将不会(静态或动态)链接到该模块/存根。模块。有时,程序包会运行./configure以识别这种依赖关系的可用性,并且如果不存在库(或用户不想启用),则会自动禁用此类功能,而无需与该库链接。如果自动生成这些指令,则总是有益的。

  4. 许可:
    预处理程序指令的一个非常有趣的示例是ffmpeg。它具有编解码器,使用该编解码器可能会侵犯专利。如果您下载源代码并进行编译安装,它将询问您是否要保留这些东西。如果条件仍然可以使您出庭,将密码隐藏在某些情况下!

  5. 代码复制粘贴:
    Aka宏。这并不是过度使用宏的建议-只是宏具有更强大的方式来应用等效于复制-过去的方式。但是要小心使用;如果知道自己在做什么,就使用它。常量当然不能这样做。但是inline如果这很容易的话,也可以使用函数。

那么什么时候使用常量呢?
几乎其他任何地方。

  1. 整洁的代码流:
    通常,使用常量时,它与常规变量几乎没有区别,因此,它是可读性更好的代码。如果您编写的例程为75行,则#ifdef每10行之后会有3或4行非常无法读取。可能给定一个主要常量,该常量由#ifdef控制,并在任何地方以自然流动的方式使用它。

  2. 缩进良好的代码:所有预处理器指令,永远不能与缩进良好的代码一起使用。即使您的编译器确实允许缩进#def,ANSI C之前的预处理器也不允许在行首与“#”字符之间留空格。前导“#”必须始终放在第一列中。

  3. 配置:
    常量/或变量有意义的另一个原因是,它们可以很容易地从链接到全局变量或将来扩展为从配置文件派生而来。

最后一件事:
永远不要使用预处理程序指令#ifdef#endif 跨越范围或{ ... }。即在的不同边的开始#ifdef或结束。这是非常糟糕的。这可能会造成混淆,有时可能会很危险。#endif{ ... }

当然,这并不是一个详尽的清单,但会显示出明显的区别,即哪种方法更易于使用。这是不是真的了解这是更好的,它常是更多的哪一个更自然的在给定环境中使用。


感谢您冗长而详尽的回答。我知道您提到的大多数内容,因此将它们从问题中省略了,因为问题不是根本不使用预处理器功能,而是有关特定类型的用法。但是我认为您提出的基本观点是正确的。
MByD 2011年

1
“切勿使用IDE预处理程序指令#ifdef到#endif跨越范围或{...””,它取决于IDE。如果IDE识别不活动的预处理程序块并将其折叠或以不同的颜色显示,则不会造成混淆。
Abyx 2011年

@Abyx IDE确实可以提供帮助。但是,在许多人在* nix(Linux等)平台下开发的情况下-编辑器等的选择很多(VI,emacs等)。因此,其他人可能会使用与您不同的编辑器。
Dipan Mehta

4

您的客户可以访问您的代码吗?如果是,那么预处理器可能是一个更好的选择。我们有类似的东西,我们为各种客户(或客户特定功能)使用编译时标记。然后,脚本可以提取客户特定的代码,然后我们将其交付。客户不知道其他客户或其他客户特定功能。


3

值得一提的其他几点:

  1. 编译器可以处理预处理器无法处理的某些常量表达式。例如,预处理器通常将无法评估sizeof()非原始类型。if()在某些情况下,这可能会强制使用。

  2. 编译器将忽略被跳过的代码中的大多数语法问题#if(某些与预处理器相关的问题仍会造成麻烦),但坚持对跳过的代码的语法正确性if()。因此,如果由于文件中其他位置的更改(例如重命名的标识符)而导致某些构建(而非其他构建)跳过的代码变得无效,则通常在所有构建中都使用sqwk,如果使用禁用则禁用,if()而使用禁用#if。根据跳过代码的原因,这可能不是好事。

  3. 一些编译器会省略无法访问的代码,而其他一些则不会。但是,即使没有代码可以使用这样分配的空间,在无法访问的代码中的静态存储持续时间声明(包括字符串文字)也可能会分配空间。

  4. 宏可以if()在其中使用测试,但是可惜没有任何机制可以使预处理器在宏中执行任何条件逻辑[请注意,在宏中使用? :运算符通常可能比更好if,但适用相同的原理]。

  5. #if指令可用于控制定义哪些宏。

我认为if()对于大多数情况而言,它通常更干净,除了那些涉及根据编译器正在使用的内容或应定义哪些宏来进行决策的情况。上述某些问题可能需要使用#if,即使在if看起来更干净的情况下也是如此。


1

长话短说:对预处理程序指令的担心基本上是2,包含多个,可能可读性较低的代码,以及更粗糙的逻辑。

如果您的项目很小,几乎没有未来的大计划,我认为这两种选择在利与弊之间都具有相同的比例,但是如果您的项目规模很大,请考虑使用其他方法,例如编写单独的头文件您仍然想使用预处理器指令,但请记住,这种方法通常会使代码的可读性降低,并确保常量的名称对程序本身的业务逻辑有意义。


这是一个很好的代码库,并且定义确实在一些单独的标头中。
MByD 2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.