为什么要在.c文件中避免使用#ifdef?

时间:2009-12-05 05:06:44

标签: c conditional-compilation

我尊重的程序员说,在C代码中,应该不惜一切代价避免使用#if#ifdef,但可能在头文件中除外。为什么在.c文件中使用#ifdef会被视为糟糕的编程习惯?

11 个答案:

答案 0 :(得分:55)

难以维持。通过在实现中散布#ifdef来更好地使用接口来抽象平台特定代码,而不是滥用条件编译。

E.g。

void foo() {
#ifdef WIN32
   // do Windows stuff
#else
   // do Posix stuff
#endif
   // do general stuff
}

不好。相反,文件foo_w32.cfoo_psx.c包含

foo_w32.c:

void foo() {
    // windows implementation
}

foo_psx.c:

void foo() {
    // posix implementation
}

foo.h中:

void foo();  // common interface

然后有2个makefile 1 Makefile.winMakefile.psx,每个文件编译相应的.c文件并链接到正确的对象。

小修正案:

如果foo()的实现取决于所有平台中出现的某些代码,例如。 common_stuff() 2 ,只需在foo()实施中调用它。

E.g。

COMMON.H:

void common_stuff();  // May be implemented in common.c, or maybe has multiple
                      // implementations in common_{A, B, ...} for platforms
                      // { A, B, ... }. Irrelevant.

foo_ {w32,psx} .c:

void foo()  {  // Win32/Posix implementation
   // Stuff
   ...
   if (bar) {
     common_stuff();
   }
}

虽然您可能正在重复对common_stuff()的函数调用,但您无法参数化每个平台的foo()定义,除非它遵循非常具体的模式。通常,平台差异需要完全不同的实现,并且不遵循这样的模式。


  1. 这里使用Makefile作为说明。您的构建系统可能根本不使用make,例如,如果您使用Visual Studio,CMake,Scons等。
  2. 即使common_stuff()实际上有多个实现,每个平台也有所不同。

答案 1 :(得分:6)

(有点问题)

我曾经看过一个提示,建议使用#if(n)def/#endif块来调试/隔离代码而不是评论。

有人建议帮助避免被评论的部分已经有文件评论的情况,并且必须实施以下解决方案:

/* <-- begin debug cmnt   if (condition) /* comment */
/* <-- restart debug cmnt {
                              ....
                          }
*/ <-- end debug cmnt

相反,这将是:

#ifdef IS_DEBUGGED_SECTION_X

                if (condition) /* comment */
                {
                    ....
                }
#endif

对我来说似乎是个好主意。希望我能记住这个来源,所以我可以链接它:(

答案 2 :(得分:3)

  1. 因为当你进行搜索结果时,你不知道代码是否存在或没有阅读。

  2. 因为它们应该用于OS / Platform依赖项,因此这类代码应该在io_win.c或io_macos.c等文件中

答案 3 :(得分:3)

我对这条规则的解释: 您的(算法)程序逻辑不应受预处理器定义的影响。代码的功能应该始终简洁。任何其他形式的逻辑(平台,调试)都应该在头文件中是可抽象的。

这更像是一条准则,而不是严格的规则,恕我直言。 但我同意基于c语法的解决方案优于预处理器魔术。

答案 4 :(得分:2)

一个合理的目标,但不是一个严格的规则

尝试在头文件中保留预处理器条件的建议是好的,因为它允许您有条件地选择接口,但不会使用令人困惑和丑陋的预处理器逻辑丢弃代码。

然而,有很多很多代码看起来像下面的例子,我认为没有明显更好的选择。我认为你引用了一个合理的指导方针,但并没有引用一个伟大的金牌诫命。

#if defined(SOME_IOCTL)
   case SOME_IOCTL:
   ...
#endif
#if defined(SOME_OTHER_IOCTL)
   case SOME_OTHER_IOCTL:
   ...
#endif
#if defined(YET_ANOTHER_IOCTL)
   case YET_ANOTHER_IOCTL:
   ...
#endif

答案 5 :(得分:2)

条件编译很难调试。必须知道所有设置才能确定程序将执行哪个代码块。

我曾经花了一周时间调试使用条件编译的多线程应用程序。问题是标识符拼写不一样。一个模块使用#if FEATURE_1,而问题区域使用#if FEATURE1(注意下划线)。

我支持让makefile通过包含正确的库或对象来处理配置。使代码更具可读性。此外,大多数代码都与配置无关,只有少数文件与配置有关。

答案 6 :(得分:1)

CPP是(通常)C或C ++之上的单独(非图灵完备)宏语言。因此,如果你不小心的话,它很容易在它和基础语言之间混淆。这是反对宏的常见论据,而不是例如无论如何,c ++模板。但#ifdef?只是去尝试阅读别人的代码,你以前从未见过有一堆ifdef。

e.g。尝试阅读这些里德 - 所罗门乘以一个由一个常数伽罗瓦值乘以的函数: http://parchive.cvs.sourceforge.net/viewvc/parchive/par2-cmdline/reedsolomon.cpp?revision=1.3&view=markup

如果你没有以下提示,你需要花一点时间来弄清楚发生了什么:有两个版本:一个是简单的,一个是带有预先计算的查找表(LONGMULTIPLY)。即使这样,也要跟踪#if BYTE_ORDER == __LITTLE_ENDIAN。我发现当我重写那个位以使用le16_to_cpu函数(其定义在#if子句中)受Linux的byteorder.h之类的启发时,我发现它更容易阅读。

如果根据构建需要不同的低级行为,请尝试将其封装在低级函数中,以便在任何地方提供一致的行为,而不是将#if内容放在较大的函数中。

答案 7 :(得分:1)

#if #endif确实使代码的读取变得复杂。但是我看到很多现实世界的代码都没有使用它的问题,并且仍然很强大。因此,可能有更好的方法可以避免使用#if #endif,但如果采取适当的谨慎措施,使用它们并不是那么糟糕。

答案 8 :(得分:0)

如果您的代码将使用不同的C编译器进行编译,并且您使用特定于编译器的功能,那么您可能需要确定哪些predefined macros可用。

答案 9 :(得分:0)

无论如何,赞成抽象而不是条件编译。然而,任何编写便携式软件的人都可以告诉你,环境排列的数量是惊人的。一些设计学科可以提供帮助,但有时候选择是在优雅和满足时间表之间。在这种情况下,可能需要妥协。

答案 10 :(得分:0)

迟到的答案,但我在寻找相关的东西时偶然发现了这一点。

考虑一下您需要提供经过全面测试的代码,100%分支覆盖等的情况。现在添加条件编译。

用于控制条件编译的每个唯一符号都会使您需要测试的代码变体数量翻倍。所以,一个符号 - 你有两个变种。两个符号,您现在有四种不同的方式来编译代码。等等。

这仅适用于#ifdef等布尔测试。如果测试的格式为#if VARIABLE == SCALAR_VALUE_FROM_A_RANGE,则可以轻松想象问题。