124

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

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

//---- 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
}

...哪种方法听起来更好,为什么?我的感觉是第一个更安全,因为总是有一些定义,并且没有危险它可以破坏其他地方的其他定义。

4

20 回答 20

88

#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_ENABLED。跟踪和删除不应该产生的调试输出比反之亦然容易得多

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

#define DEBUG_ENABLED true

代替

#define DEBUG_ENABLED (1)
于 2008-09-25T19:28:50.547 回答
58

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

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

然后,每当您需要调试代码时,将其放入D();. 而且你的程序没有被可怕的#ifdef.

于 2010-07-03T17:57:55.480 回答
38

#ifdef只检查是否定义了一个令牌,给定

#define FOO 0

然后

#ifdef FOO // is true
#if FOO // is false, because it evaluates to "#if 0"
于 2008-09-25T21:00:29.250 回答
23

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

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

于 2010-07-01T18:32:52.913 回答
17

出于执行条件编译的目的,#if 和 #ifdef几乎相同,但并不完全相同。如果您的条件编译依赖于两个符号,那么 #ifdef 将无法正常工作。例如,假设你有两个条件编译符号,PRO_VERSION 和 TRIAL_VERSION,你可能有这样的东西:

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

使用上面的#ifdef 变得更加复杂,尤其是让#else 部分工作。

我处理广泛使用条件编译的代码,我们混合了#if 和#ifdef。我们倾向于将#ifdef/#ifndef 用于简单的情况,并在评估两个或多个符号时使用#if。

于 2008-09-25T20:48:58.717 回答
15

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

一致性比任何一个特定的选择都更重要,所以我建议你和你的团队一起选择一种风格,并坚持下去。

于 2008-09-25T18:34:23.690 回答
8

我自己更喜欢:

#if defined(DEBUG_ENABLED)

因为它使创建查找相反条件的代码更容易发现:

#if !defined(DEBUG_ENABLED)

对比

#ifndef(DEBUG_ENABLED)
于 2008-09-25T18:30:47.193 回答
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);
于 2008-09-25T19:55:51.480 回答
5

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

于 2008-09-25T18:29:25.590 回答
4

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

有趣的。:-)

于 2008-09-25T19:00:45.467 回答
3

这根本不是风格问题。不幸的是,这个问题也是错误的。您无法在更好或更安全的意义上比较这些预处理器指令。

#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 

或者您可以将宏设置为编译器标志。也见这里

于 2013-06-10T21:04:36.043 回答
2

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

于 2008-09-25T18:31:30.233 回答
2

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

于 2008-09-25T18:38:30.543 回答
2

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

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

输出:

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

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

于 2018-01-17T09:55:22.917 回答
1

有点 OT,但使用预处理器打开/关闭日志记录在 C++ 中绝对是次优的。有一些不错的日志工具,例如 Apache 的log4cxx,它们是开源的,不会限制您分发应用程序的方式。它们还允许您在不重新编译的情况下更改日志记录级别,如果您关闭日志记录,开销非常低,并且让您有机会在生产中完全关闭日志记录。

于 2008-09-25T19:04:15.973 回答
1

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

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

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

于 2017-08-04T16:14:42.977 回答
0

或者,您可以声明一个全局常量,并使用 C++ if,而不是预处理器 #if。编译器应该为你优化未使用的分支,你的代码会更干净。

以下是Stephen C. Dewhurst 的C++ Gotchas关于使用#if 的说法。

于 2008-09-25T18:32:53.270 回答
0

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

于 2008-09-25T18:34:18.557 回答
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 标志将被文件中的标志覆盖。因此,您不必担心在提交之前更改标头。

于 2019-08-16T05:40:56.603 回答
0

与许多事情一样,答案取决于。#ifdef对于保证在特定单元中定义或未定义的事物非常有用。例如,包括警卫。如果包含文件至少存在一次,则保证定义符号,否则不定义。

但是,有些东西没有这种保证。想想这个符号HAS_FEATURE_X。有多少个州?

  1. 不明确的
  2. 定义
  3. 用一个值定义(比如 0 或 1)。

因此,如果您正在编写代码,尤其是共享代码,其中一些可能#define HAS_FEATURE_X 0意味着功能 X 不存在而另一些可能只是没有定义它,您需要处理所有这些情况。

#if !defined(HAS_FEATURE_X) || HAS_FEATURE_X == 1

仅使用 an#ifdef可能会导致一些细微的错误,因为某人或某个团队有将未使用的事物定义为 0 的约定,因此意外地打开(或关闭)某些东西。在某些方面,我喜欢这种#if方法,因为这意味着程序员积极地做了一个决定。留下未定义的东西是被动的,从外部的角度来看,有时可能不清楚这是故意还是疏忽。

于 2021-07-20T06:58:06.127 回答