我尊重的一位程序员说,在 C 代码中应该尽可能避免使用 #if
和 #ifdef
,除非在头文件中。为什么在 .c 文件中使用 #ifdef
会被认为是不良编程实践呢?
我尊重的一位程序员说,在 C 代码中应该尽可能避免使用 #if
和 #ifdef
,除非在头文件中。为什么在 .c 文件中使用 #ifdef
会被认为是不良编程实践呢?
难以维护。最好使用接口来抽象平台相关的代码,而不是滥用条件编译,在实现中到处都散布着 #ifdef
块。
例如:
void foo() {
#ifdef WIN32
// do Windows stuff
#else
// do Posix stuff
#endif
// do general stuff
}
不太好。相反,使用文件foo_w32.c
和foo_psx.c
foo_w32.c:
void foo() {
// windows implementation
}
foo_psx.c:
void foo() {
// posix implementation
}
foo.h:
:void foo(); // common interface
接下来需要有两个makefile1: Makefile.win
, Makefile.psx
,每个makefile编译适当的.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()
的定义。一般来说,平台之间的差异需要完全不同的实现,而不是遵循这样的模式。
make
,例如如果您使用Visual Studio、CMake、Scons等。common_stuff()
实际上有多个针对不同平台的实现。(有点偏离问题)
我曾经看到一条提示,建议在调试/隔离代码时使用#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
对我来说,这似乎是个不错的想法。但我希望我能记得来源,这样我就可以链接它 :(
#if 0 ... #else ... #endif
来临时更改代码进行调试。然后将 0 改为 1 来回到原始代码。 - Steve Jessop#if 0
的使用很好。我曾经使用过像 ifdef/else 这样的方式来保留原始代码的完整性,但没有想到可以启用切换功能。(我上面的示例只是为了展示删除代码)我得记住这个切换功能。谢谢 :) - paul#if 0
是C语言中常用的习语,用于暂时禁用代码段,因为/* */
不能嵌套(这个答案中的措辞使它看起来像某种秘密技巧)。 - mk12因为如果不阅读代码,你无法确定它是否被包含在搜索结果中。
因为它们应该用于操作系统/平台依赖项,因此这种代码应该放在像 io_win.c 或 io_macos.c 这样的文件中。
我对这个规则的理解是:您的(算法)程序逻辑不应受预处理器定义的影响。您的代码功能应始终简洁明了。任何其他形式的逻辑(平台,调试)都应在头文件中进行抽象化。
在我看来,这更像是一条指导方针而非严格的规则。但我同意,基于c语法的解决方案比预处理器魔法更可取。
尝试将预处理器条件放在头文件中是个好建议,因为它允许你有条件地选择接口,而不会在代码中添加令人困惑和丑陋的预处理器逻辑。
然而,有很多代码看起来像下面这个虚构的例子,我认为没有明显更好的替代方案。我认为你提到了一个合理的指导方针,但并不是一个伟大的金科玉律。
#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
#if defined(...) && !defined(...)
,比如当操作系统定义了两个错误代码或信号的值相同时。但是如果可能的话,最好将这种混乱放在单独的文件中,这样大部分时间你就不必关注它了... - Donal Fellows#if FEATURE_1
,而问题区域使用 #if FEATURE1
(注意下划线)。makefile
处理配置,包括正确的库或对象。这使得代码更易读。此外,大部分代码变得与配置无关,只有少数文件与配置相关。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等放在较大的函数内部。
#define X(X + 1)
),并且一切都在一个命名空间中,因此没有“with x =(x + 1):{...}”的方式,因此我找不到传递可变参数给递归函数的方法(例如:“fact(x)=(x * fact(x-1))”)。 CPP宏可以是递归的(#include __FILE__
),但不那么有用。 - David X#ifdef
。如果测试的形式为#if VARIABLE == SCALAR_VALUE_FROM_A_RANGE
,你可以轻松想象出问题所在。如果你的代码将使用不同的 C 编译器进行编译,并且你使用了特定于编译器的功能,那么你可能需要确定可用的 预定义宏 。
foo_{w32,psx,...}.c
文件中。虽然在特定平台上的代码重复度可能比你想象的要高,但DRY原则鼓励重构以最小化代码重复。 - Donal Fellows