#ifdef vs #if-作为启用/禁用特定代码段编译的方法,哪种方法更好/更安全?
这可能是风格问题,但我们的开发团队存在一些分歧,我想知道是否还有其他人对此有任何想法...
基本上,我们有一些调试打印语句,这些语句在正常开发过程中会关闭。我个人更喜欢执行以下操作:
//---- 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选项,我们可以将其设置为0以关闭功能,同时仍然可以检测到该开关在那儿。
就我个人而言,我总是#define DEBUG 1
,因此我可以使用#if或者#ifdef来捕获它
我自己更喜欢:
#if defined(DEBUG_ENABLED)
由于它使创建查找相反条件的代码更加容易,因此更容易发现:
#if !defined(DEBUG_ENABLED)
与
#ifndef(DEBUG_ENABLED)
第一个对我来说似乎更清楚。与已定义/未定义相比,将其标记为标志似乎更自然。
另外,我们可以声明一个全局常量,并使用C ++ if代替预处理程序#if。编译器应该为我们优化未使用的分支,并且代码将更加整洁。
这是Stephen C. Dewhurst撰写的C ++ Gotchas关于使用#if的内容。
我一直使用#ifdef和编译器标志来定义它。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
我认为这完全是风格问题。两者之间都没有真正的明显优势。
一致性比任何一种选择都重要,因此,我建议我们与团队团结起来,选择一种风格并坚持下去。
两者是完全等效的。在惯用中,#ifdef仅用于检查定义性(以及我们在示例中使用的内容),而#if用于更复杂的表达式,例如#if define(A)&&!defined(B)。
#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 ++代码。
圣经后
现在,当我重新阅读我的文章时,我想知道是否不应该尝试找到一些永远无法将正确的C ++添加到我的定义中的值。就像是
#define MY_MACRO <<<<<<
可以与#ifdef和#ifndef一起使用,但是如果在函数内部使用,则不让代码进行编译...我在g ++上成功尝试了此操作,并给出了错误:
main.cpp|410|error: stray ‘@’ in program|
有趣的。
:-)
有点费时,但是在C ++中使用预处理器打开/关闭日志记录绝对不是最佳选择。有很好的日志记录工具,例如Apache的log4cxx,它们是开源的,并且不限制我们分发应用程序的方式。它们还使我们无需重新编译即可更改日志记录级别,如果我们关闭日志记录,则开销非常低,并有机会在生产中完全关闭日志记录。
我最初的反应是" #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 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和#ifdef几乎相同,但不完全相同。如果条件编译依赖于两个符号,则#ifdef将无法正常工作。例如,假设我们有两个条件编译符号PRO_VERSION和TRIAL_VERSION,则可能会有以下内容:
#if defined(PRO_VERSION) && !defined(TRIAL_VERSION) ... #else ... #endif
使用#ifdef,上述操作变得更加复杂,尤其是使#else部分开始工作。
我在广泛使用条件编译的代码上工作,我们混合使用#if和#ifdef。对于简单情况,我们倾向于使用#ifdef /#ifndef,而在评估两个或者更多符号时,则倾向于使用#if。
#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
但是随后我们忘记了将头文件包含在file.cpp中:
#if COOL_FEATURE // definitely awesome stuff here... #endif
然后,我们遇到问题,在这种情况下,编译器将未定义的COOL_FEATURE解释为"假",并且无法包含代码。是的,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
由于使用了未定义的函数宏,预处理器可能会出错。
他们都很丑。相反,请执行以下操作:
#ifdef DEBUG #define D(x) do { x } while(0) #else #define D(x) do { } while(0) #endif
然后,每当需要调试代码时,都将其放入D();
中。而且程序不会被#ifdef的可怕迷宫污染。