#ifdef vs #if-作为启用/禁用特定代码段编译的方法,哪种方法更好/更安全?


112

这可能是风格问题,但我们的开发团队存在一些分歧,我想知道是否还有其他人对此有任何想法...

基本上,我们有一些调试打印语句,这些语句在正常开发过程中会关闭。我个人更喜欢执行以下操作:

//---- 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...等等一起使用
安德鲁(Andrew)

Answers:


82

我的最初反应#ifdef当然是,但我认为#if实际上对此有一些明显的优势-这是原因:

首先,您可以DEBUG_ENABLED在预处理器编译测试中使用。示例-通常,启用调试后,我希望超时时间更长,因此使用#if,我可以这样写

  DoSomethingSlowWithTimeout(DEBUG_ENABLED? 5000 : 1000);

... 代替 ...

#ifdef DEBUG_MODE
  DoSomethingSlowWithTimeout(5000);
#else
  DoSomethingSlowWithTimeout(1000);
#endif

其次,如果要从a迁移#define到全局常量,则处于更好的位置。#define大多数C ++程序员通常都不喜欢s。

第三,您说您的团队存在分歧。我的猜测是,这意味着不同的成员已经采用了不同的方法,您需要进行标准化。#if首选的裁定意味着,#ifdef即使使用DEBUG_ENABLEDfalse ,使用的代码也将编译并运行。而且它的很多容易追查并卸下时,它不应该比反之亦然生产调试输出。

哦,还有一点可读性。您应该能够在中使用true / false而不是0/1 #define,并且由于该值是单个词法标记,因此这是您一次不需要括号的情况。

#define DEBUG_ENABLED true

代替

#define DEBUG_ENABLED (1)

该常量可能不会用于启用/禁用调试,因此在将#define设为0的情况下触发#ifdef可能不是那么有益。至于对/错,这些是在C99中添加的,在C89 / C90中不存在。
Michael Carman's

Micheal:他/她主张使用#ifdef吗?!
乔恩·凯奇

7
是的,一个问题#ifdef是它可以处理未定义的事物。无论是不是故意定义它们,还是因为错字或您有什么。
bames53 2013年

6
您添加的答案是错误的。#if DEBUG_ENBALED不是预处理器检测到的错误。如果DEBUG_ENBALED未定义,则0#if指令中扩展为令牌。
R .. GitHub停止帮助ICE,

6
@R ..在许多编译器中,当未定义DEBUG_ENABLED时,可以为“ #if DEBUG_ENABLED”启用警告。在GCC中使用“ -Wundef”。在Microsoft Visual Studio中,使用“ / w14668”打开C4668作为1级警告。
2014年

56

他们都很丑。相反,请执行以下操作:

#ifdef DEBUG
#define D(x) do { x } while(0)
#else
#define D(x) do { } while(0)
#endif

然后,每当需要调试代码时,都将其放入D();。而且您的程序不会被的可怕迷宫污染#ifdef


6
@MatthieuM。实际上,我认为原始版本很好。分号将被解释为空语句。但是,忘记分号可能会使其变得危险。
Casey Kuball 2014年

30

#ifdef 只是检查给定的令牌是否已定义

#define FOO 0

然后

#ifdef FOO // is true
#if FOO // is false, because it evaluates to "#if 0"

20

我们在多个文件中都遇到了同样的问题,总有一个问题是人们忘记包含“功能标志”文件(使用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

由于使用了未定义的函数宏,预处理器会出错。


17

为了执行条件编译,#if和#ifdef 几乎相同,但不完全相同。如果您的条件编译依赖于两个符号,则#ifdef将无法正常工作。例如,假设您有两个条件编译符号PRO_VERSION和TRIAL_VERSION,则可能会有以下内容:

#if defined(PRO_VERSION) && !defined(TRIAL_VERSION)
...
#else
...
#endif

使用#ifdef,上述操作变得更加复杂,尤其是使#else部分开始工作。

我在广泛使用条件编译的代码上工作,我们混合使用#if和#ifdef。对于简单情况,我们倾向于使用#ifdef /#ifndef,而在评估两个或更多符号时,则倾向于使用#if。


1
#if defined什么是defined它是一个关键的词或?
nmxprime 2014年

15

我认为这完全是风格问题。两者之间都没有真正的明显优势。

一致性比任何一种选择都重要,因此,我建议您与团队团结起来,选择一种风格并坚持下去。


8

我自己更喜欢:

#if defined(DEBUG_ENABLED)

由于它使创建查找相反条件的代码更加容易,因此更容易发现:

#if !defined(DEBUG_ENABLED)

#ifndef(DEBUG_ENABLED)

8
就我个人而言,我认为错过那个小小的感叹号会更容易!
乔恩·凯奇

6
用语法高亮显示?:)在语法高亮显示中,“ ifndef”中的“ n”很难辨认,因为它们都是相同的颜色。
Jim Buck

好的,我的意思是,当您与#if define ..进行比较时,#ifndef比#if!defined更容易发现,但是鉴于所有#ifdefined /#if!defined与#ifdef /#ifndef相比,两者同样容易误读!
乔恩·凯奇

@JonCage我知道这条评论已经过去了几年了,但我想指出,您可以编写它#if ! defined来使它变得!更加突出和不容错过。
法老

@Pharap-这肯定看起来像是一种改进:)
Jon Cage

7

这是风格问题。但是我建议一种更简洁的方法:

#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);

3
毕竟,打印宏也许不是最好的例子-实际上,我们已经在代码库中为更标准的调试代码进行了此操作。我们使用的#if / #ifdefined位,而您可能需要打开额外的调试领域..
乔恩·凯奇

5

#if可让您选择将其设置为0以关闭功能,同时仍检测到开关在那里。
我个人总是#define DEBUG 1这样,所以我可以用#if或#ifdef来捕捉它


1
这失败了,因为#define DEBUG = 0现在将不再运行#if,而是将运行#ifdef
窃听

1
没错,我可以完全删除DEBUG或将其设置为0以禁用它。
Martin Beckett

应该是#define DEBUG 1。不是#define DEBUG=1
Keshava GN

4

#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(甚至是Jimif建议的#if定义),最重要的是,给它们起如此长的名字,以至于任何人都不会在他/她的右脑中使用它是“偶然”的,绝不会影响合法的C ++代码。

圣经后

现在,当我重新阅读我的文章时,我想知道是否不应该尝试找到一些永远无法将正确的C ++添加到我的定义中的值。就像是

#define MY_MACRO @@@@@@@@@@@@@@@@@@

可以与#ifdef和#ifndef一起使用,但是如果在函数内部使用,则不让代码进行编译...我在g ++上成功尝试了此操作,并给出了错误:

main.cpp|410|error: stray ‘@’ in program|

有趣。:-)


我同意宏可能很危险,但是第一个示例在调试时会很明显,当然它只会给出一个错误。您为什么还会期望更多?由于宏,我看到了很多更糟糕的错误...
Jon Cage

的确,一种解决方案与另一种解决方案之间的差异几乎是微不足道的。但是在这种情况下,当我们谈论两种相互竞争的编码样式时,即使是琐碎的事也不能忽略,因为在那之后,剩下的只是个人品味了(在这一点上,我认为不应将其归一化) )
paercebal

4

这根本不是风格问题。同样不幸的是,这个问题是错误的。您不能从更好或更安全的意义上比较这些预处理程序指令。

#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 

或者您可以将宏设置为编译器标志。另请参阅此处


2

第一个对我来说似乎比较清楚。与已定义/未定义相比,将其标记为标志似乎更自然。


2

两者完全相同。在惯用中,#ifdef仅用于检查定义性(以及我在示例中使用的内容),而#if用于更复杂的表达式,例如#if define(A)&&!defined(B)。


1
OP并没有要求“ #ifdef”和“ #if defined”之间哪个更好,而是“#ifdef /#if define”和“ #if”之间哪个更好。
小腿

1

有点费时,但是在C ++中使用预处理器打开/关闭日志记录绝对不是最佳选择。有很好的日志记录工具,例如Apache的log4cxx,它们是开源的,并且不限制您分发应用程序的方式。它们还使您无需重新编译即可更改日志记录级别,如果关闭日志记录,则开销非常低,并有机会在生产中完全关闭日志记录。


1
我同意了,我们实际上做的是在我们的代码,我只是想你可以使用#如果等的东西的例子
乔恩·凯奇

1

我曾经使用#ifdef,但是当我切换到Doxygen进行文档编制时,发现无法记录注释掉的宏(或者至少Doxygen会发出警告)。这意味着我无法记录当前未启用的功能开关宏。

尽管可以只为Doxygen定义宏,但这意味着代码非活动部分中的宏也将被记录下来。我个人想显示功能开关,否则仅记录当前选择的内容。此外,如果只有在Doxygen处理文件时必须定义许多宏,这会使代码变得很混乱。

因此,在这种情况下,最好始终定义宏并使用#if


0

我一直使用#ifdef和编译器标志来定义它...


出于某种特殊原因(出于好奇)?
乔恩·凯奇

2
老实说,我从未想过-只是在我工作过的地方,它是如何完成的。它确实具有这样的优势,您无需为生产构建更改代码,只需要做“ make DEBUG”进行调试,或“ make PRODUCTION”进行常规
tloach

0

另外,您可以声明一个全局常量,并使用C ++ if代替预处理程序#if。编译器应该为您优化未使用的分支,并且您的代码将更加干净。

这是Stephen C. Dewhurst 撰写C ++ Gotchas关于使用#if的内容。


1
那是一个糟糕的解决方案,它存在以下问题:1.仅在函数中起作用,无法删除不需要的类变量,等等。2.编译器可能会发出有关无法访问的代码的警告。3.如果仍然需要编译,则代码在其中,这意味着你必须保持你所有的调试功能定义等等
唐·诺伊费尔德

首先,问题特别是关于调试printfs,因此不需要的类变量在这里不是问题。其次,鉴于现代编译器的功能,您应该尽可能少地使用#ifdefs。在大多数情况下,您可以使用构建配置或模板专门化来代替。
Dima

0

向驱动程序指定条件定义的方式不同的情况有所不同:

diff <( echo | g++ -DA= -dM -E - ) <( echo | g++ -DA -dM -E - )

输出:

344c344
< #define A 
---
> #define A 1

这意味着,这-DA是同义词-DA=1,如果省略value,则在#if A使用时可能会导致问题。


0

我喜欢#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标志将被文件中的那个覆盖。因此,您不必担心在提交之前变回标头。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.