为什么在 .c 文件中应避免#ifdef?

发布于 2024-08-13 08:09:10 字数 134 浏览 3 评论 0原文

我尊敬的一位程序员说,在 C 代码中,应该不惜一切代价避免使用 #if#ifdef,除非可能在头文件中。为什么在 .c 文件中使用 #ifdef 被认为是不好的编程习惯?

A programmer I respect said that in C code, #if and #ifdef should be avoided at all costs, except possibly in header files. Why would it be considered bad programming practice to use #ifdef in a .c file?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

扎心 2024-08-20 08:09:10

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

例如,

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 个 makefile1Makefile.winMakefile.psx,每个文件都编译相应的 .c 文件并链接到正确的对象。

小修改:

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

例如

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() 的定义code> 每个平台,除非它遵循非常特定的模式。一般来说,平台差异需要完全不同的实现,并且不遵循这样的模式。


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

Hard to maintain. Better use interfaces to abstract platform specific code than abusing conditional compilation by scattering #ifdefs all over your implementation.

E.g.

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

Is not nice. Instead have files foo_w32.c and foo_psx.c with

foo_w32.c:

void foo() {
    // windows implementation
}

foo_psx.c:

void foo() {
    // posix implementation
}

foo.h:

void foo();  // common interface

Then have 2 makefiles1: Makefile.win, Makefile.psx, with each compiling the appropriate .c file and linking against the right object.

Minor amendment:

If foo()'s implementation depends on some code that appears in all platforms, E.g. common_stuff()2, simply call that in your foo() implementations.

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();
   }
}

While you may be repeating a function call to common_stuff(), you can't parameterize your definition of foo() per platform unless it follows a very specific pattern. Generally, platform differences require completely different implementations and don't follow such patterns.


  1. Makefiles are used here illustratively. Your build system may not use make at all, such as if you use Visual Studio, CMake, Scons, etc.
  2. Even if common_stuff() actually has multiple implementations, varying per platform.
疯狂的代价 2024-08-20 08:09:10

(有点偏离所问的问题)

我曾经看到一个提示,建议使用 #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

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

(Somewhat off the asked question)

I saw a tip once suggesting the use of #if(n)def/#endif blocks for use in debugging/isolating code instead of commenting.

It was suggested to help avoid situations in which the section to be commented already had documentation comments and a solution like the following would have to be implemented:

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

Instead, this would be:

#ifdef IS_DEBUGGED_SECTION_X

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

Seemed like a neat idea to me. Wish I could remember the source so I could link it :(

叹沉浮 2024-08-20 08:09:10
  1. 因为当你执行搜索结果时,如果不阅读代码,你就不知道代码是输入还是输出。

  2. 因为它们应该用于操作系统/平台依赖项,因此此类代码应该位于 io_win.c 或 io_macos.c 之类的文件中

  1. Because then when you do search results you don't know if the code is in or out without reading it.

  2. Because they should be used for OS/Platform dependencies, and therefore that kind of code should be in files like io_win.c or io_macos.c

小糖芽 2024-08-20 08:09:10

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

恕我直言,这更像是一个指导方针,而不是严格的规则。
但我同意基于 C 语法的解决方案比预处理器魔法更受青睐。

My interpretation of this rule:
Your (algorithmic) program logic should not be influenced by preprocessor defines. The functioning of your code should always be concise. Any other form of logic (platform, debug) should be abstractable in header files.

This is more a guideline than a strict rule, IMHO.
But I agree that c-syntax based solutions are preferred over preprocessor magic.

傲娇萝莉攻 2024-08-20 08:09:10

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

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

我非常支持让 makefile 通过包含正确的库或对象来处理配置。使代码更具可读性。此外,大部分代码变得独立于配置,只有少数文件依赖于配置。

The conditional compilation is hard to debug. One has to know all the settings in order to figure out which block of code the program will execute.

I once spent a week debugging a multi-threaded application that used conditional compilation. The problem was that the identifier was not spelled the same. One module used #if FEATURE_1 while the problem area used #if FEATURE1 (Notice the underscore).

I a big proponent of letting the makefile handle the configuration by including the correct libraries or objects. Makes to code more readable. Also, the majority of the code becomes configuration independent and only a few files are configuration dependent.

空城之時有危險 2024-08-20 08:09:10

一个合理的目标,但不像严格的规则那么好

尝试将预处理器条件保留在头文件中的建议很好,因为它允许您有条件地选择接口,但不会让代码充满混乱和丑陋的预处理器逻辑。

然而,有很多很多代码看起来像下面的虚构示例,我认为没有明显更好的替代方案。我认为你引用了一个合理的指导方针,但不是一个伟大的金碑诫命。

#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

A reasonable goal but not so great as a strict rule

The advice to try and keep preprocessor conditionals in header files is good, as it allows you to select interfaces conditionally but not litter the code with confusing and ugly preprocessor logic.

However, there is lots and lots and lots of code that looks like the made-up example below, and I don't think there is a clearly better alternative. I think you have cited a reasonable guideline but not a great gold-tablet-commandment.

#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
姜生凉生 2024-08-20 08:09:10

CPP 是(通常)C 或 C++ 之上的一种独立的(非图灵完备)宏语言。因此,如果您不小心,很容易将它与基本语言混淆。无论如何,这是反对宏而不是 C++ 模板的常见论点。但是#ifdef?只要去尝试阅读别人的代码,你以前从未见过其中有一堆 ifdef。

例如,尝试阅读这些 Reed-Solomon 乘以一个块乘以一个常数 Galois 值函数:
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 内容放在较大的函数中。

CPP is a separate (non-Turing-complete) macro language on top of (usually) C or C++. As such, it's easy to get mixed up between it and the base language, if you're not careful. That's the usual argument against macros instead of e.g. c++ templates, anyway. But #ifdef? Just go try to read someone else's code you've never seen before that has a bunch of ifdefs.

e.g. try reading these Reed-Solomon multiply-a-block-by-a-constant-Galois-value functions:
http://parchive.cvs.sourceforge.net/viewvc/parchive/par2-cmdline/reedsolomon.cpp?revision=1.3&view=markup

If you didn't have the following hint, it will take you a minute to figure out what's going on: There are two versions: one simple, and one with a pre-computed lookup table (LONGMULTIPLY). Even so, have fun tracing the #if BYTE_ORDER == __LITTLE_ENDIAN. I found it a lot easier to read when I rewrote that bit to use a le16_to_cpu function, (whose definition was inside #if clauses) inspired by Linux's byteorder.h stuff.

If you need different low-level behaviour depending on the build, try to encapsulate that in low-level functions that provide consistent behaviour everywhere, instead of putting #if stuff right inside your larger functions.

π浅易 2024-08-20 08:09:10

无论如何,优先考虑抽象而不是条件编译。然而,任何编写过便携式软件的人都可以告诉您,环境排列的数量是惊人的。一些设计规则会有所帮助,但有时需要在优雅和满足时间表之间做出选择。在这种情况下,可能需要做出妥协。

By all means, favor abstraction over conditional compilation. As anyone who has written portable software can tell you, however, the number of environmental permutations is staggering. Some design discipline can help, but sometimes the choice is between elegance and meeting a schedule. In such cases, a compromise might be necessary.

以为你会在 2024-08-20 08:09:10

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

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

这仅适用于布尔测试,例如#ifdef。如果测试的形式为#if VARIABLE == SCALAR_VALUE_FROM_A_RANGE,您可以很容易地想象出问题。

Consider the situation where you are required to provide fully tested code, with 100% branch coverage etc. Now add in conditional compilation.

Each unique symbol used to control conditional compilation doubles the number of code variants you need to test. So, one symbol - you have two variants. Two symbols, you now have four different ways to compile your code. And so on.

And this only applies for boolean tests such as #ifdef. You can easily imagine the problem if a test is of the form #if VARIABLE == SCALAR_VALUE_FROM_A_RANGE.

旧竹 2024-08-20 08:09:10

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

If your code will be compiled with different C compilers, and you use compiler-specific features, then you may need to determine which predefined macros are available.

确实,#if #endif 确实使代码的阅读变得复杂。然而,我见过很多现实世界的代码,使用它没有任何问题,并且仍然很强大。因此,可能有更好的方法来避免使用#if #endif,但如果采取适当的小心,使用它们并没有那么糟糕。

It's true that #if #endif does complicate the reading of the code. However I have seen a lot of real world code that have no issues using this and are still going strong. So there may be better ways to avoid using #if #endif but using them is not that bad if proper care is taken.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文