#ifdef vs #if - 作为启用/禁用特定代码段编译的方法更好/更安全?
-
02-07-2019 - |
题
这可能是一种风格问题,但我们的开发团队有一些分歧,我想知道是否有其他人对此事有任何想法......
基本上,我们有一些调试打印语句,我们在正常开发期间关闭。我个人更喜欢做以下事情:
//---- 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
}
......哪些方法听起来更好,为什么?我的感觉是,第一个更安全,因为总有一些东西被定义,并且没有危险它可以破坏别处的其他定义。
解决方案
我最初的反应是 #ifdef
,当然,但我认为 #if
实际上有一些明显的优势 - 这就是为什么:
首先,您可以在预处理器和编译的测试中使用 DEBUG_ENABLED
。示例 - 通常,在启用调试时我想要更长的超时,所以使用 #if
,我可以写这个
DoSomethingSlowWithTimeout(DEBUG_ENABLED? 5000 : 1000);
......而不是......
#ifdef DEBUG_MODE
DoSomethingSlowWithTimeout(5000);
#else
DoSomethingSlowWithTimeout(1000);
#endif
其次,如果您想从 #define
迁移到全局常量,那么您处于更好的位置。大多数C ++程序员通常不赞成 #define
。
而且,第三,你说你的团队存在分歧。我的猜测是,这意味着不同的成员已采用不同的方法,您需要标准化。判断 #if
是首选,这意味着使用 #ifdef
的代码将编译 - 并运行 - 即使 DEBUG_ENABLED
为false。并且很多更容易跟踪并删除在不应该反之亦然时产生的调试输出。
哦,还有一个小的可读性点。您应该能够在 #define
中使用true / false而不是0/1,并且因为该值是单个词法标记,所以有一次您不需要围绕它的括号。
#define DEBUG_ENABLED true
而不是
#define DEBUG_ENABLED (1)
其他提示
他们都很可怕。相反,这样做:
#ifdef DEBUG
#define D(x) do { x } while(0)
#else
#define D(x) do { } while(0)
#endif
然后,无论何时需要调试代码,请将其放在 D();
中。并且你的程序没有受到 #ifdef
的可怕迷宫的污染。
#ifdef
只是检查是否定义了一个令牌,给定
#define FOO 0
然后
#ifdef FOO // is true
#if FOO // is false, because it evaluates to "#if 0"
我们在多个文件中遇到了同样的问题,并且人们忘记包含“功能标志”时总是存在问题。文件(代码库>> 41,000个文件很容易做到。)
如果你有feature.h:
#ifndef FEATURE_H
#define FEATURE_H
// turn on cool new feature
#define COOL_FEATURE 1
#endif // FEATURE_H
但是你忘了在header.cpp中包含头文件:
#if COOL_FEATURE
// definitely awesome stuff here...
#endif
然后你遇到问题,编译器将COOL_FEATURE解释为未定义为“false”。在这种情况下,无法包含代码。是的gcc确实支持一个导致未定义宏错误的标志......但大多数第三方代码定义或不定义功能,所以这不是那么便携。
我们采用了一种便携式方法来纠正这种情况,并测试功能的状态:功能宏。
如果您将上述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
由于使用了未定义的函数宏,预处理器会出错。
为了执行条件编译,#if和#ifdef 几乎相同,但不完全相同。如果条件编译依赖于两个符号,则#ifdef也不会起作用。例如,假设您有两个条件编译符号PRO_VERSION和TRIAL_VERSION,您可能会这样:
#if defined(PRO_VERSION) && !defined(TRIAL_VERSION)
...
#else
...
#endif
使用#ifdef变得更加复杂,特别是让#else部分工作。
我在广泛使用条件编译的代码上工作,我们混合了#if& #ifdef来。对于简单情况,我们倾向于使用#ifdef /#ifndef;对于正在评估的两个或更多符号,我们倾向于使用#if。
我认为这完全是一种风格问题。两者都没有明显的优势。
一致性比任何一种选择都重要,所以我建议你和你的团队一起选择一种风格并坚持下去。
我自己更喜欢:
#if defined(DEBUG_ENABLED)
因为它更容易创建寻找相反条件的代码更容易发现:
#if !defined(DEBUG_ENABLED)
VS
#ifndef(DEBUG_ENABLED)
这是一种风格问题。但我建议采用更简洁的方法:
#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
为您提供了将其设置为0以关闭功能的选项,同时仍然检测到交换机在那里。
我个人总是 #define DEBUG 1
所以我可以用#if或#ifdef来抓住它
#if和#define MY_MACRO(0)
使用#if表示您创建了“定义”宏,即将在代码中搜索的内容将被“(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 ++代码不会将其看作是滞后代码语句。上面的代码相同,只需要一个简单的定义:
#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 ++代码。
Post Scriptum
现在,当我重新阅读我的帖子时,我想知道我是否应该尝试找到一些永远不会正确的C ++值添加到我的定义中。像
这样的东西#define MY_MACRO @@@@@@@@@@@@@@@@@@
可以与#ifdef和#ifndef一起使用,但是如果在函数内部使用则不允许代码编译...我在g ++上成功尝试了这个,并且它给出了错误:
main.cpp|410|error: stray ‘@’ in program|
有趣。 : - )
第一个对我来说更清楚。与定义/未定义相比,它似乎更自然地成为旗帜。
两者完全相同。在惯用语中,#ifdef仅用于检查定义(以及我在示例中使用的内容),而#if用于更复杂的表达式,例如#if defined(A)&& !定义(B)。
有点旧,但使用预处理器打开/关闭日志记录在C ++中肯定是次优的。有很好的日志工具,如Apache的 log4cxx ,它们是开源的,不受限制你如何分发你的申请。它们还允许您在不重新编译的情况下更改日志记录级别,如果您关闭日志记录,则开销非常低,并且您有机会在生产中完全关闭日志记录。
这根本不是风格问题。不幸的是,这个问题也是错误的。您无法在更好或更安全的意义上比较这些预处理器指令。
#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
或者您可以将宏设置为编译器标志。另请查看此处。
我曾经使用 #ifdef
,但是当我切换到Doxygen进行文档编制时,我发现无法记录已注释掉的宏(或者,至少Doxygen会产生警告)。这意味着我无法记录当前未启用的功能切换宏。
虽然可以仅为Doxygen定义宏,但这意味着代码的非活动部分中的宏也将被记录。我个人想要显示功能开关,否则只记录当前选择的内容。此外,如果只有在Doxygen处理文件时才需要定义许多宏,它会使代码变得非常混乱。
因此,在这种情况下,最好始终定义宏并使用 #if
。
我总是使用#ifdef和编译器标志来定义它......
或者,您可以声明一个全局常量,并使用C ++ if而不是预处理器#if。编译器应该为您优化未使用的分支,并且您的代码将更清晰。
以下是 C ++ Gotchas 由Stephen C. Dewhurst谈到使用#if's。
如果为驱动程序指定条件定义的方式不同,则存在差异:
diff <( echo | g++ -DA= -dM -E - ) <( echo | g++ -DA -dM -E - )
输出:
344c344
< #define A
---
> #define A 1
这意味着, -DA
是 -DA = 1
的同义词,如果省略了值,那么在 #if的情况下可能会导致问题
用法。