#ifdef宏是否等同于注释?

8
假设 MACRO 没有被定义,下面两个表达式是否等价?
如果 MACRO 未被定义,这两个表达式是否相同?
#ifdef MACRO
    Not valid C or C++ code
#endif

/*
    Not valid C or C++ code
*/

在GCC 4.7.1中,它似乎是等效的,但是否有更多的预处理器呢?

1
如果MACRO未定义,则是的,代码将无法编译,在这两种情况下,预处理器用于条件编译,如果定义为真,则代码编译,否则不编译。 - Grijesh Chauhan
8个回答

13

这取决于你所说的“不是有效的C或C++代码”是什么意思。

注释中的文本不必符合语言的大部分规则。它甚至没有被分词。以下代码也是完全有效的:

/* This comment doesn't contain a valid sequence of preprocessing tokens
   (because of the apostrophe).  */

它需要遵守的唯一规则是控制注释结束位置的规则。人们经常在行注释中被反斜杠换行符绊倒(事实上,SO的语法高亮器曾经对此处理不正确!)

// Line comment with ascii art ending with a \
   Oops! This line is commented out too!

而块注释不嵌套则很少使用(仅因每个C教程都警告您不要这样做),请注意保留HTML标签:

/* you can't nest /* block comments */ these words are not commented */

另一方面,“跳过”的预处理条件“组”中的文本确实必须符合语言的某些规则。标准的确切措辞(C99 §6.10.1p5)是:“依次检查每个指令的条件。如果它计算为假(零),则跳过它所控制的组:只通过确定指令名称的方式来处理指令,以便跟踪嵌套条件的级别;忽略其余的指令的预处理记号,以及组中的其他预处理记号。”
有两个重要的因素。首先,文本被标记化,因此它必须是有效的预处理记号序列。
#if 0
This skipped conditional group doesn't contain a valid sequence of
preprocessing tokens (because of the apostrophe).
#endif

是语法错误。

$ gcc -fsyntax-only test.c
test.c:2:37: warning: missing terminating ' character
 this skipped conditional group doesn't contain a valid sequence of
                                     ^

其次,指令仍然部分处理“为了跟踪嵌套条件的级别”,这意味着您可以执行以下操作:可以

#if 0 // forget this entire mess
    #ifdef __linux__
    do_linux_specific_thing();
    #elif defined __APPLE__
    do_osx_specific_thing();
    #elif defined _WIN32
    do_windows_specific_thing();
    #endif
#endif

无法做到这个

    #ifdef __linux__
    do_linux_specific_thing();
    #elif defined __APPLE__
    do_osx_specific_thing();
#if 0 // forget windows
    #elif defined _WIN32
    do_windows_specific_thing();
    #endif
#endif

在这种情况下,您不会收到错误提示,但是...

$ gcc -E -P -U__linux__ -D__APPLE__ -D_WIN32 test.c
    do_osx_specific_thing();
    do_windows_specific_thing();

许多关于编程语言的指南告诉你可以使用#if 0来“注释掉”你想暂时禁用的大段代码。他们这么说是因为块注释不能嵌套。如果你试图使用块注释禁用一段代码区域,但是该区域内有一个块注释,则注释将会提前结束,很可能导致代码无法编译。在C语言没有行注释的时代,这一点更为重要;某些项目仅使用行注释进行注释,保留块注释以便禁用代码。
但是,由于#if 0...#endif内部的代码仍然被分词化,嵌套的预处理器条件必须仍然平衡,所以你必须小心地选择放置#if 0#endif的位置。通常这不是问题,因为在你禁用代码之前,代码已经被编译过了,所以它不应该有任何东西会导致分词错误。

7
在一般情况下,两者是等效的。然而,如果你的“不合法的C或C++代码”包含注释,第一种形式将起作用,而第二种则不会。这是因为C标准不允许嵌套注释。
 /* Comments /* inside */ comments are not allowed. */

顺便提一句,在这种情况下,经常使用#if 0而不是#ifdef MACRO
#if 0
    Invalid C source code
#endif

请查看这个问题

1
注释用于注释,宏用于预处理选项。这样的宏内部应该有有效的 C 代码。至少在某些情况下是有效的。不匹配的/*根本不应该存在,无论是否被#if排除。 - Elazar
1
如果你担心未匹配的/*,那么你也应该担心在“无效的C源代码”中间的#endif - Elazar
哇,我实际上不得不去查一下imbricate的意思。这种情况并不经常发生 :-) - paxdiablo
MACRO的方式让我有可能检查这个块是否对我有趣。实际上,我将使用__MACRO__进入保留的命名空间。 - user877329
@Elazar: #ifdef / #endif 指令可以嵌套,但是注释不能。我不是在提到“不匹配的/*”,因为它也无法与匹配的 /* 一起工作。 - md5

5

是的,它们是等价的,预处理阶段会在编译器真正看到代码之前消除非有效的C或C++代码

预处理包括删除注释以及被#if过滤掉的代码。

但是, 如果有人使用-DMACRO编译代码,#ifdef版本会让你陷入麻烦,最好使用#if 0来通过预处理器删除代码。


1
相关的标准部分为 C11 6.10.1 Conditional inclusion /6:

按顺序检查每个指令的条件。如果它评估为 false(零),则跳过其所控制的组。

这意味着,如果任何形式的代码(如ififdef等)评估为 false,则不会对该组进行任何处理,并且在后续处理阶段中完全删除。它不会转换成注释。

0
不会的,在你最终的代码中,#ifdef内部的代码不会有任何痕迹:
// before
#ifdef MACRO
    Not valid C or C++ code
#endif
// after

预编译后:

// before
// after

里面没有剩余的代码。


1
评论中也不会有代码,所以我认为它们是相等的;两者都为空。 - Enigma
1
它是“等效的”但不是“相等的”。OP错误地认为代码将被放置在注释部分。实际发生的是,代码被完全剥离。 - Gui13
1
你甚至不会留下 // before// after 注释,因为注释会在预处理阶段之前被剥离。 - gx_
实际上这并不是真的。注释也会被预处理器删除。 - leemes
1
@gx_ 你的意思是在预处理指令阶段之前,即在处理宏和#if之前。但是这两个阶段都被认为是预处理器的一部分。 - leemes
@leemes 谢谢您的纠正。那确实是我想表达的(抱歉……) - gx_

0
如果未定义MACRO,则它们应该是等效的。通常注释掉大块代码的方法通常是:
#if 0
code(); /* possibly with comments. */
#endif

这使您能够禁用包含注释的大部分代码,即使它们。因此,与普通注释相比,它更适用于禁用代码的部分。

但是有一个警告。我曾经遇到过编译器无法处理类似以下代码的情况:

#ifdef __GNUC__
#nonstandardpreprocessordirective
#endif

"nonstandardpreprocessordirective" 是一个只在 GCC 上有效的预处理器指令。我不确定标准对此有何规定,但它在过去的实际应用中曾经引起问题。我不记得是哪个编译器了。


0

它们很接近,但还不完全相同。假设没有定义宏(或者假设您使用其他答案中推荐的#if 0):

#ifdef MACRO
Not valid C or C++ code
*/  - does no harm
#endif  - oops
more invalid code
#endif

以及注释:

/*
    Not valid C or C++ code
    #endif - does no harm
    */ - oops
*/

注释是用于注释的,#ifdef 是用于禁用 合法 代码的。任意文本都不应该存在于源代码中。


0

是的,大多数预处理器(如果不是全部)都会删除注释和评估为0的指令。两者之间的区别主要在于功能。

我的建议是使用指令来“注释”代码(#if 0 {} #endif),并将注释用于注释(非常合理,对吧?)。主要原因如下:

  • 通过修改代码中的1行即可激活/停用指令。块注释需要在代码的不同行中插入/删除2个元素。
  • 指令可以嵌套保留IF逻辑,并且还可以包含块注释。/块注释/无法嵌套,当您注释可能包含其他注释的大段代码时,这可能会成为问题。
#if 0
...
#if 1
#endif
...
#endif
  • 简单的#if 0指令可以轻松转换为define或eval指令,从而允许更动态的条件代码处理。
//Classic verbose code line comment

#if 0
//Directive verbose line or block comment   
#endif

#define verbose 0
#if verbose
//Convenient eval directive to turn on/off this and other verbose blocks
#endif
  • 大多数IDE不会突出显示注释块的语法,但会突出显示指令代码的语法。缩进或自动完成等其他功能也是如此。这使得/**/块的可读性与#if 0 #endif块相比相当差。此外,使用指令更容易编辑已注释的代码(例如添加一行或修复某些内容)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接