#ifdef 与 #if - 作为启用/禁用特定代码部分编译的方法,哪个更好/更安全?
Posted
技术标签:
【中文标题】#ifdef 与 #if - 作为启用/禁用特定代码部分编译的方法,哪个更好/更安全?【英文标题】:#ifdef vs #if - which is better/safer as a method for enabling/disabling compilation of particular sections of code? 【发布时间】:2010-09-13 04:47:40 【问题描述】:这可能是风格问题,但我们的开发团队中存在一些分歧,我想知道其他人是否对此事有任何想法......
基本上,我们有一些调试打印语句,我们在正常开发过程中会关闭这些语句。就个人而言,我更喜欢执行以下操作:
//---- SomeSourceFile.cpp ----
#define DEBUG_ENABLED (0)
...
SomeFunction()
int someVariable = 5;
#if(DEBUG_ENABLED)
printf("Debugging: someVariable == %d", someVariable);
#endif
不过,有些团队更喜欢以下内容:
// #define DEBUG_ENABLED
...
SomeFunction()
int someVariable = 5;
#ifdef DEBUG_ENABLED
printf("Debugging: someVariable == %d", someVariable);
#endif
...这些方法中哪一种听起来更好,为什么?我的感觉是第一个更安全,因为总是有一些定义,并且没有危险可以破坏其他地方的其他定义。
【问题讨论】:
注意: 与#if
一起使用,您还可以以一致的方式使用#elif
,这与#ifdef
不同。因此,不要只使用#define BLAH
,而是使用#define BLAH 1
和#if BLAH
,等等...
【参考方案1】:
我最初的反应是#ifdef
,当然,但我认为#if
实际上对此有一些显着的优势——原因如下:
首先,您可以在预处理器和编译测试中使用DEBUG_ENABLED
。示例 - 通常,我希望在启用调试时有更长的超时时间,所以使用#if
,我可以写这个
DoSomethingSlowWithTimeout(DEBUG_ENABLED? 5000 : 1000);
...而不是...
#ifdef DEBUG_MODE
DoSomethingSlowWithTimeout(5000);
#else
DoSomethingSlowWithTimeout(1000);
#endif
其次,如果您想从 #define
迁移到全局常量,您将处于更好的位置。 #define
s 通常被大多数 C++ 程序员所反对。
第三,你说你的团队存在分歧。我的猜测是这意味着不同的成员已经采用了不同的方法,你需要标准化。判定#if
是首选意味着即使DEBUG_ENABLED
为假,使用#ifdef
的代码也将编译并运行。与反之亦然相比,跟踪和删除不应该产生的调试输出要容易得多。
哦,还有一点可读性。您应该能够在 #define
中使用真/假而不是 0/1,并且由于该值是单个词法标记,因此您不需要在其周围加上括号。
#define DEBUG_ENABLED true
而不是
#define DEBUG_ENABLED (1)
【讨论】:
该常量可能不会用于启用/禁用调试,因此触发带有#define 为0 的#ifdef 可能不是那么良性。至于真假,是在C99中添加的,在C89/C90中不存在。 ...关于真/假的好点 - 特别是我们的嵌入式平台实际上并没有定义 bool! 是的,#ifdef
的一个问题是它适用于未定义的事物。无论它们是不是故意定义的,还是因为拼写错误或你有什么。
您对答案的补充是错误的。 #if DEBUG_ENBALED
不是预处理器检测到的错误。如果未定义 DEBUG_ENBALED
,则在 #if
指令中扩展为标记 0
。
@R.. 在许多编译器中,当未定义 DEBUG_ENABLED 时,您可以启用“#if DEBUG_ENABLED”警告。在 GCC 中使用“-Wundef”。在 Microsoft Visual Studio 中,使用“/w14668”将 C4668 作为 1 级警告打开。【参考方案2】:
他们都很可怕。而是这样做:
#ifdef DEBUG
#define D(x) do x while(0)
#else
#define D(x) do while(0)
#endif
然后,当您需要调试代码时,将其放入D();
。而且你的程序没有被#ifdef
的可怕迷宫污染。
【讨论】:
@MatthieuM。其实我觉得原版还不错。分号将被解释为空语句。但是,忘记分号可能会很危险。【参考方案3】:#ifdef
只是检查是否定义了一个令牌,给定
#define FOO 0
然后
#ifdef FOO // is true
#if FOO // is false, because it evaluates to "#if 0"
【讨论】:
【参考方案4】:我们在多个文件中遇到过同样的问题,并且总是存在人们忘记包含“功能标志”文件的问题(代码库超过 41,000 个文件很容易做到)。
如果你有 feature.h:
#ifndef FEATURE_H
#define FEATURE_H
// turn on cool new feature
#define COOL_FEATURE 1
#endif // FEATURE_H
但是你忘了在file.cpp中包含头文件:
#if COOL_FEATURE
// definitely awesome stuff here...
#endif
那么你有一个问题,在这种情况下,编译器将未定义的 COOL_FEATURE 解释为“假”,并且无法包含代码。是的,gcc 确实支持导致未定义宏出错的标志...但是大多数第 3 方代码要么定义要么不定义特性,因此它不会那么便携。
我们采用了一种可移植的方式来纠正这种情况以及测试功能的状态:函数宏。
如果您将上述 feature.h 更改为:
#ifndef FEATURE_H
#define FEATURE_H
// turn on cool new feature
#define COOL_FEATURE() 1
#endif // FEATURE_H
但是你又忘记在 file.cpp 中包含头文件了:
#if COOL_FEATURE()
// definitely awseome stuff here...
#endif
由于使用了未定义的函数宏,预处理器会出错。
【讨论】:
【参考方案5】:出于执行条件编译的目的,#if 和#ifdef 几乎相同,但并不完全相同。如果您的条件编译依赖于两个符号,那么 #ifdef 将无法正常工作。例如,假设你有两个条件编译符号,PRO_VERSION 和 TRIAL_VERSION,你可能有这样的东西:
#if defined(PRO_VERSION) && !defined(TRIAL_VERSION)
...
#else
...
#endif
使用#ifdef 会变得更加复杂,尤其是让#else 部分起作用。
我编写的代码广泛使用条件编译,我们混合使用了#if 和#ifdef。我们倾向于将#ifdef/#ifndef 用于简单的情况,并在评估两个或多个符号时使用#if。
【讨论】:
in#if defined
什么是 defined
是关键字还是?【参考方案6】:
我认为这完全是风格问题。两者都没有明显的优势。
一致性比任何一个特定的选择都更重要,所以我建议你和你的团队一起选择一种风格,并坚持下去。
【讨论】:
【参考方案7】:我自己更喜欢:
#if defined(DEBUG_ENABLED)
因为它使创建查找相反条件的代码更容易发现:
#if !defined(DEBUG_ENABLED)
对比
#ifndef(DEBUG_ENABLED)
【讨论】:
我个人认为忽略那个小感叹号更容易! 带语法高亮? :) 在语法高亮中,“ifndef”中的“n”更难发现,因为它都是相同的颜色。 好吧,我的意思是当你与 #if defined 进行比较时,#ifndef 比 #if !defined 更容易发现。但考虑到所有 #if defined/#if !defined 与 #ifdef/#ifndef , 任何一个都同样难以阅读! @JonCage 我知道这条评论已经有好几年了,但我想指出您可以将其写为#if ! defined
以使!
更加突出和难以错过。
@Pharap - 这看起来确实是一种改进 :)【参考方案8】:
这是风格问题。但我推荐一种更简洁的方法:
#ifdef USE_DEBUG
#define debug_print printf
#else
#define debug_print
#endif
debug_print("i=%d\n", i);
您只需执行一次,然后始终使用 debug_print() 打印或不执行任何操作。 (是的,这两种情况都可以编译。)这样,你的代码就不会被预处理器指令弄乱了。
如果您收到警告“表达式无效”并想摆脱它,这里有一个替代方案:
void dummy(const char*, ...)
#ifdef USE_DEBUG
#define debug_print printf
#else
#define debug_print dummy
#endif
debug_print("i=%d\n", i);
【讨论】:
也许打印宏毕竟不是最好的例子——我们实际上已经在我们的代码库中为我们更标准的调试代码做了这个。我们将#if / #ifdefined 位用于您可能想要打开额外调试的区域..【参考方案9】:#if
让您可以选择将其设置为 0 以关闭该功能,同时仍检测到开关在那里。
就我个人而言,我总是#define DEBUG 1
所以我可以用#if 或#ifdef 来捕捉它
【讨论】:
这会失败,因为 #define DEBUG=0 现在不会运行 #if 但会运行 #ifdef 就是这样,我可以完全删除 DEBUG 或将其设置为 0 以禁用它。 应该是#define DEBUG 1
。不是#define DEBUG=1
【参考方案10】:
#if 和 #define MY_MACRO (0)
使用#if 意味着您创建了一个“define”宏,即将在代码中搜索并替换为“(0)”的东西。这是我讨厌在 C++ 中看到的“宏观地狱”,因为它会通过潜在的代码修改污染代码。
例如:
#define MY_MACRO (0)
int doSomething(int p_iValue)
return p_iValue + 1 ;
int main(int argc, char **argv)
int MY_MACRO = 25 ;
doSomething(MY_MACRO) ;
return 0;
在 g++ 上给出以下错误:
main.cpp|408|error: lvalue required as left operand of assignment|
||=== Build finished: 1 errors, 0 warnings ===|
只有一个错误。
这意味着您的宏已成功与您的 C++ 代码交互:对函数的调用成功。在这个简单的例子中,它很有趣。但是我自己的宏在我的代码中默默地玩的经验并不充满乐趣和满足感,所以......
#ifdef 和 #define MY_MACRO
使用#ifdef 意味着你“定义”了一些东西。并不是说你给它一个价值。它仍然是污染的,但至少,它会“被无所取代”,并且不会被 C++ 代码视为 lagitimate 代码语句。上面相同的代码,用一个简单的定义,它:
#define MY_MACRO
int doSomething(int p_iValue)
return p_iValue + 1 ;
int main(int argc, char **argv)
int MY_MACRO = 25 ;
doSomething(MY_MACRO) ;
return 0;
给出以下警告:
main.cpp||In function ‘int main(int, char**)’:|
main.cpp|406|error: expected unqualified-id before ‘=’ token|
main.cpp|399|error: too few arguments to function ‘int doSomething(int)’|
main.cpp|407|error: at this point in file|
||=== Build finished: 3 errors, 0 warnings ===|
所以...
结论
我宁愿在我的代码中不使用宏,但由于多种原因(定义标头保护或调试宏),我不能。
但至少,我喜欢让它们与我的合法 C++ 代码交互最少。这意味着使用没有价值的#define,使用#ifdef 和#ifndef(甚至是Jim Buck 建议的#if 定义),最重要的是,给它们起这么长的名字,那么陌生,没有人在他/她的头脑中会使用它是“偶然”的,而且绝不会影响合法的 C++ 代码。
后记
现在,当我重新阅读我的帖子时,我想知道我是否不应该尝试找到一些永远不会是正确的 C++ 的值来添加到我的定义中。类似的东西
#define MY_MACRO @@@@@@@@@@@@@@@@@@
这可以与#ifdef 和#ifndef 一起使用,但如果在函数内部使用则不允许代码编译...我在g++ 上成功尝试过,它给出了错误:
main.cpp|410|error: stray ‘@’ in program|
有趣。 :-)
【讨论】:
我同意宏可能很危险,但是第一个示例对于调试来说是相当明显的,当然它只会给出一个错误。为什么你会期待更多?由于宏,我看到了很多更严重的错误...... 确实,一种解决方案与另一种解决方案之间的区别几乎是微不足道的。但是在这种情况下,当我们谈论两种相互竞争的编码风格时,即使是琐碎的也不能被忽视,因为在那之后,剩下的就是个人品味(在这一点上,我认为它不应该被规范化)【参考方案11】:这根本不是风格问题。不幸的是,这个问题也是错误的。您无法在更好或更安全的意义上比较这些预处理器指令。
#ifdef macro
表示“如果定义了宏”或“如果存在宏”。宏的值在这里无关紧要。它可以是任何东西。
#if macro
如果总是比较一个值。在上面的例子中,它是标准的隐式比较:
#if macro !=0
#if 的用法示例
#if CFLAG_EDITION == 0
return EDITION_FREE;
#elif CFLAG_EDITION == 1
return EDITION_BASIC;
#else
return EDITION_PRO;
#endif
您现在可以将 CFLAG_EDITION 的定义放入您的代码中
#define CFLAG_EDITION 1
或者您可以将宏设置为编译器标志。还有see here。
【讨论】:
【参考方案12】:第一个对我来说似乎更清楚。与已定义/未定义相比,将其设为标志似乎更自然。
【讨论】:
【参考方案13】:两者完全相同。在惯用用法中,#ifdef 仅用于检查定义(以及我在您的示例中使用的内容),而 #if 用于更复杂的表达式,例如 #if defined(A) && !defined(B)。
【讨论】:
OP 并没有要求“#ifdef”和“#if defined”之间哪个更好,而是在“#ifdef/#if defined”和“#if”之间。【参考方案14】:有点 OT,但是使用预处理器打开/关闭日志记录在 C++ 中绝对不是最佳选择。有一些不错的日志工具,例如 Apache 的 log4cxx,它们是开源的,不会限制您分发应用程序的方式。它们还允许您在不重新编译的情况下更改日志记录级别,如果您关闭日志记录,开销非常低,并且让您有机会在生产中完全关闭日志记录。
【讨论】:
我同意,我们实际上在我们的代码中这样做了,我只是想要一个你可以使用 #if 等的例子【参考方案15】:我曾经使用#ifdef
,但是当我切换到 Doxygen 文档时,我发现注释掉的宏无法记录(或者,至少,Doxygen 会产生警告)。这意味着我无法记录当前未启用的功能切换宏。
虽然可以仅为 Doxygen 定义宏,但这意味着代码的非活动部分中的宏也将被记录。我个人想展示功能开关,否则只记录当前选择的内容。此外,如果只有在 Doxygen 处理文件时必须定义许多宏,这会使代码变得非常混乱。
因此,在这种情况下,最好始终定义宏并使用#if
。
【讨论】:
【参考方案16】:在不同的情况下为驱动指定条件定义的方式是不同的:
diff <( echo | g++ -DA= -dM -E - ) <( echo | g++ -DA -dM -E - )
输出:
344c344
< #define A
---
> #define A 1
这意味着,-DA
是 -DA=1
的同义词,如果省略值,则在使用 #if A
时可能会导致问题。
【讨论】:
【参考方案17】:我一直使用#ifdef 和编译器标志来定义它...
【讨论】:
有什么特别的原因(出于好奇)? 老实说,我从来没有想过它 - 只是我工作过的地方是如何做到的。它确实提供了一个优势,即您不必为生产构建进行代码更改,您所要做的就是“make DEBUG”进行调试,或者“make PRODUCTION”进行常规【参考方案18】:或者,您可以声明一个全局常量,并使用 C++ if,而不是预处理器 #if。编译器应该为您优化未使用的分支,您的代码会更干净。
这是 Stephen C. Dewhurst 的 C++ Gotchas 对使用 #if 的看法。
【讨论】:
这是一个糟糕的解决方案,它有以下问题: 1. 仅适用于函数,您无法删除不需要的类变量等 2. 编译器可能会抛出有关无法访问代码的警告 3. 中的代码如果仍然需要编译,这意味着您必须保留所有调试功能,等等。 首先问题是关于调试 printfs,所以不需要的类变量在这里不是问题。其次,鉴于现代编译器的功能,您应该尽可能少地使用#ifdefs。在大多数情况下,您可以改用构建配置或模板特化。【参考方案19】:当您可能需要多级调试时,我喜欢#define DEBUG_ENABLED (0)
。例如:
#define DEBUG_RELEASE (0)
#define DEBUG_ERROR (1)
#define DEBUG_WARN (2)
#define DEBUG_MEM (3)
#ifndef DEBUG_LEVEL
#define DEBUG_LEVEL (DEBUG_RELEASE)
#endif
//...
//now not only
#if (DEBUG_LEVEL)
//...
#endif
//but also
#if (DEBUG_LEVEL >= DEBUG_MEM)
LOG("malloc'd %d bytes at %s:%d\n", size, __FILE__, __LINE__);
#endif
使调试内存泄漏变得更容易,而无需在调试其他事物时使用所有这些日志行。
此外,定义周围的#ifndef
可以更轻松地在命令行中选择特定的调试级别:
make -DDEBUG_LEVEL=2
cmake -DDEBUG_LEVEL=2
etc
如果不是因为这个,我会利用#ifdef
,因为编译器/make 标志将被文件中的标志覆盖。因此,您不必担心在提交之前更改标头。
【讨论】:
【参考方案20】:与许多事情一样,答案取决于。 #ifdef
非常适合保证在特定单元中定义或未定义的事物。例如,包括警卫。如果包含文件至少存在一次,则保证定义符号,否则不定义。
但是,有些东西并没有这样的保证。想想符号HAS_FEATURE_X
。有多少个州?
-
未定义
已定义
用一个值定义(比如 0 或 1)。
因此,如果您正在编写代码,尤其是共享代码,其中一些可能#define HAS_FEATURE_X 0
表示功能 X 不存在而其他可能只是没有定义它,您需要处理所有这些情况。
#if !defined(HAS_FEATURE_X) || HAS_FEATURE_X == 1
仅使用 #ifdef
可能会导致一些细微的错误,因为某人或某个团队有将未使用的东西定义为 0 的约定,因此意外地切换了(或切换)某些东西。在某些方面,我喜欢这个 #if
方法,因为这意味着程序员积极做出了决定。留下未定义的东西是被动的,从外部的角度来看,有时可能不清楚这是故意还是疏忽。
【讨论】:
以上是关于#ifdef 与 #if - 作为启用/禁用特定代码部分编译的方法,哪个更好/更安全?的主要内容,如果未能解决你的问题,请参考以下文章
使用 jQuery 启用/禁用特定“验证组”中的 asp.net 验证控件?