为什么人们在特性标记测试中使用 #ifdef?

17
人们普遍推荐使用#ifdef进行条件编译搜索#ifdef可以证实它的使用非常普遍。
然而,#ifdef NAME(或等价的#if defined(NAME)和相关的#ifndef NAME(以及#if !defined(NAME))存在严重缺陷:

header.h

#ifndef IS_SPECIAL
#error You're not special enough
#endif

source.cpp

#include "header.h"

这句话的意思是:“显然,gcc -DIS_SPECIAL source.cpp是可以通过的。”

source1.cpp

#define IS_SPECIAL 1
#include "header.h"

但是,也会这样。

source0.cpp

#define IS_SPECIAL 0
#include "header.h"

这是错误的做法。有些 C++ 编译器会以 C 模式处理文件(由于扩展名或命令行选项),实际上执行了 #define __cplusplus 0。我曾经看到过一些东西出错的情况。
#ifdef __cplusplus
extern "C" {
#endif
/* ... */
#ifdef __cplusplus
}
#endif

该文段的翻译如下:

C模式下处理,其中extern "C"是无效的语法,因为实际上__cplusplus被自动定义为0

另一方面,所有编译器都正确地执行此操作:

#if __cplusplus
extern "C" {
#endif
/* ... */
#if __cplusplus
}
#endif

为什么在这种情况下人们仍然使用#ifdef?他们是不知道#if对未定义的名称也能正常工作吗?还是说在条件编译中,#if#ifdef相比存在实际的劣势?
显然,#ifdef确实有其有效的用途,例如为可配置参数提供默认值:
#ifndef MAX_FILES
#define MAX_FILES 64
#endif

我只是在讨论旗帜测试的情况。

10
为什么说这是“完全错误的事情”?这个条件询问某件事是否被定义,在两种情况下它都有定义。人们会期望得到相同的结果,不是吗? - Joshua Taylor
3
那意味着编译器正确地实现了测试,但并不意味着用户选择了正确的测试。请查看有关 __cplusplus == 0 的讨论,以了解为什么这是错误的测试。或者你认为将设置从 1 更改为 0 不应该关闭它吗? - Ben Voigt
3
对于给予负评的人:我想知道你认为这个问题为什么不够研究充分?是因为我没有引用标准中处理#if和未定义宏的部分吗?还是因为你认为答案没有实际价值?还是因为这个问题写得很糟糕,普通的C和C++程序员无法理解?如果你能提供反馈,我很乐意尝试改进它。 - Ben Voigt
2
@Joshua:关于“One would expect the same results, no?”,我必须说(以autoconf为例)不,我不认为#define HAVE_READLINE 0#define HAVE_READLINE 1会做同样的事情。 - Ben Voigt
3
标准草案n1570中的第6.10.8节(预定义宏名称)第3款规定:“实现不得预定义宏__cplusplus,也不得在任何标准头文件中定义它。”在C模式下定义它的编译器是有问题的,因此这个特定的例子并不是支持使用#if而不是#ifndef的一个很强的论据。 - Iwillnotexist Idonotexist
显示剩余15条评论
2个回答

3
为什么在这种情况下人们仍然使用 #ifdef?
个人观点:从命令行更容易控制。 我更喜欢 -DOPTION 而不是 -DOPTION=1
此外,名称的存在显然是二进制的。 我不需要处理 {0,非零,未定义}。
他们是否仅仅不知道 #if 在未定义名称上也能正常工作?
我不知道。 这个的语义是什么? 一个未定义的名称被假定为0吗? 我要给那个几乎不了解预处理器的人解释吗?
还是 #if 相对于 #ifdef 在条件编译中实际上有不利之处?
对我而言,#ifdef/#ifndef 对于名称存在的二进制本质是清晰的好处。 此外,我主要使用这两种构造中的任何一种来进行包含保护。 在该模式中,#ifndef 是最干净的选择。

4
在我使用过的任何预处理器中,“-DOPTION”和“-DOPTION=1”之间没有区别。两者都定义了一个名为“OPTION”的宏,其值为“1”。 - Ben Voigt
1
对于C++,16.1p4定义了在未定义名称上使用#if的语义如下:“在宏展开和一元运算符 defined 所有替换都已执行后,所有剩余的标识符和关键字(除了 true 和 false)都将替换为pp数字0,然后每个预处理标记被转换为一个标记。” - Ben Voigt
@BenVoigt,你感兴趣的所有预处理器都定义了一个用-D定义为1的宏,并不保证它会一直保持这种状态。从上面可以看出,你关心将C89代码移植到各种旧编译器的可移植性,但你也可能关心将你的代码移植到各种shell脚本和预处理器的可移植性。 - Iwillnotexist Idonotexist

2
我不知道人们为什么普遍喜欢用#ifdef而不是#if,但至少我可以说一下我为什么这样做。基于刚才的内省(因为你问了,我以前从未明确考虑过),有两个原因:
1)我希望我的宏(我尽量少用)具有尽可能直观的语义,并且相应地“无类型”。我假设如果宏有任何类型,那么它们要么是“无类型函数”(注意:在这里我更喜欢使用模板,但每种方法都有时候需要……),要么只是布尔标志。因此,即使将值1分配给宏也会让我感到困扰。(例如,如果您有#define _cplusplus 2,那么这意味着什么?是否与1有任何不同?)
2)宏是“标志”,这意味着我主要用它们来指定命令行(或IDE)中的条件编译标志。实际上,在我的软件团队中,当我们编写C++时,我们基本上“禁止”使用宏来完成其他任务。即使在条件编译的情况下,如果我们可以通过模块化等其他方式解决问题,我们也会尽量避免使用宏。
这两个原因都与同一基本假设有关,即尽可能避免使用宏(在C++中),因此不应需要类型或不透明的语义。如果您不持有这种假设(我知道在编写C时这种情况不太常见),那么情况会发生变化,我想你对#if的看法可能更有说服力。

顺便提一下,当你在命令行上使用“-D”定义一个标志,并且没有指定任何特定的值时,它将获得值“1”。 - Ben Voigt
@BenVoigt,“undefined”并不明确为假。也许在这里它被定义为假,但我想不出其他情况下它是真的。通常,“undefined”是第三种状态,特别是当考虑到人们使用多种语言时。 - Adam
@Turix:我说过-1是假的吗?我只是说1是真的。 - Ben Voigt
@Adam:使用 #ifdef 的人显然是想让(未定义)为假。#if 也是这样处理的。区别在于它们如何处理 0 - Ben Voigt
2
例如,如果您有#define _cplusplus 2,那么这意味着什么?这是否与1有任何不同?__cplusplus带有两个下划线的宏会扩展为您正在使用的C++版本的日期。预标准编译器和最近的GCC将其定义为1,但是后来的编译器将其定义为199711L201103L。只是提供信息... - Potatoswatter
显示剩余4条评论

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