重新定义C++关键字是否合法?

16
本文中,Guru of the week指出:“#define保留字是非法的。”这是真的吗?我没有在规范文件中找到任何信息,并且我已经看到程序员重新定义了“new”等保留字。

1
绝对可以使用#define来改变保留字的含义。事实上,在国际混淆C代码大赛(The International Obfuscated C Code Contest)的参赛作品中经常被使用。这是可能的,因为由#define定义的宏在实际的C编译器运行之前会被一个单独的程序替换掉。 - Some programmer dude
你确定他们没有过载新运算符吗?由于预处理器首先运行,重新“#define”关键字的问题在于你用替换字符串替换了关键字,不期望这样做的代码很可能会出现问题。通常情况下,这样做是一个坏主意,你为什么要这样做呢? - ted
@ted:这个想法是在源文件中重新定义new关键字,以便调用特定于平台的实现:在Macintosh上#define new newMac,在PC上#define new newPc。在其他翻译单元中,相应的函数将定义特定于平台的内存分配器。我想这个想法是为了在无法重命名每个对new的调用时,在任何地方继续使用new同时具有特定于平台的行为。 - qdii
1
早些时候,我们曾经使用 #define for if (false) else for 来修复 Visual Studio 6 在 for 循环中声明变量的错误作用域。 - Jack Aidley
你说的“我已经看到程序员重新定义新的”是什么意思?你是否见过使用 #define的此类示例?还是你指的是重载new运算符?这两件事情完全不同。 - Code-Apprentice
@MonadNewb 我已经看到过 #define new newPS3。游戏行业有点像动物园。 - qdii
5个回答

19

17.4.3.1.1 宏名称 [lib.macro.names]

1 在头文件中定义的每个宏名称都保留给实现用于任何用途,如果翻译单元包括该头文件,则如此。164)
2 包含头文件的翻译单元不得包含定义在该头文件中声明或定义的任何名称的宏。也不应将这样的翻译单元定义为与关键字词法上相同的名称的宏。

顺便说一下,new 是一个操作符,用户可以通过提供自己的版本来重新定义(重载)它。


8
请注意,该规则仅适用于包含标准头文件的源代码。如果翻译单元不包括标准头文件,则重新定义关键字是完全合法的。(然而,这并不利于代码的可读性。) - James Kanze
1
所以只要我的翻译单元不包括标准头文件,我就可以定义 #define new 吗?@JamesKanze:为什么要标准头文件? - qdii
@victor:标准(模板)库? - ted
2
@victor - 这种语言允许编译器“知道”每个标准头文件中包含什么,因此它不必每次重新编译它们。因此,您不能通过重新定义这些头文件中的某些单词来更改它们。您对自己的头文件所做的更改由您决定! - Bo Persson
1
在C++03标准中,术语“头文件”在**17.4.1.2 Headers [lib.headers]**中定义为:“C++标准库的元素在一个头文件中声明或定义(视情况而定)。”这与我们通常认为的头文件(.h文件)不符。标准没有区分.cpp文件和.h文件之间的差异;它们都被定义为“源文件”。 - Joseph Mansfield
显示剩余3条评论

12

对应的C++11章节:

17.6.4.3.1 宏名称 [macro.names]

 

1 包含标准库头文件的翻译单元不得#define或#undef任何标准库头文件中声明的名称。
    2 翻译单元不得#define或#undef与关键字在词法上完全相同的名称。

C++03的第一段已被删除。第二段已分为两个部分。第一半现在已经更改为明确说明它仅适用于标准头文件。第二点已经扩大到包括任何翻译单元,而不仅仅是那些包括头文件的翻译单元。

然而,此章节的概述17.6.4.1 [constraints.overview])指出:

 

本节描述使用C++标准库功能的C++程序的限制。

因此,如果您没有使用C++标准库,那么您可以随心所欲。

因此,在C++11的背景下回答您的问题:如果您使用C++标准库,您不能在任何翻译单元中定义(或取消定义)与关键字完全相同的名称。


使用C++标准库的定义是什么?例如,程序int main() {}是否使用C++标准库?我之所以问这个问题是因为[basic.start.main]/5提到了std::exit - L. F.

2
他们的说法实际上是错误的,或者至少没有完全解释清楚。它被禁止的真正原因是违反了一个定义规则(顺便提一下,这也是它被认为是非法的第二个原因)。
要看到它实际上是被允许的(重新定义关键字),至少如果您不使用标准库,您必须看一个完全不同的标准部分,即翻译阶段。它说,在预处理之前,输入只被分解成预处理器标记,查看这些标记时,privatefubar之间没有区别,它们都是预处理器的identifiers。稍后,当输入被分解成token时,替换已经发生。
有人指出,对于要使用标准库的程序有限制,但并不明显,例如重新定义private的示例是否在使用它(而不是“Person #4:The Language Lawyer”片段将其用于输出到cout)。
最后一个示例中提到,这种技巧不会被其他翻译单元所覆盖或压制。考虑到这一点,您应该考虑可能在其他地方使用了标准库,这将使此限制生效。

1

如果你不想让别人使用goto语句,这里有一个小技巧。只需将以下代码放在他的代码中他不会注意到的地方。

#define goto { int x = *(int *)0; } goto

现在每次他尝试使用goto语句,程序就会崩溃。


1
@JackAidley 你说得对,但是你有没有问过自己这件事情为什么有趣呢?答案是人类是一种虐待狂的生物,这也是令人害怕的部分...点赞你指出了人性。 - mip
5
这是一种未定义的行为,最好避免。 - Abhijit
3
如果你认为goto是不好的,那么你应该不适合写程序。如果你能说服我永远不应该使用goto,我会非常惊讶。 - Iharob Al Asimi
1
@iharob 同意。有限状态机是使用 goto 的潜在好用之处。 - chbaker0
@chbaker0:是的,但goto并不一定是实现有限状态机的最佳方式;在循环中使用switch语句可以使当前状态更加明确。有关何时适用goto的更多讨论,请参见此答案 - Keith Thompson
显示剩余3条评论

-1

据我所知,这并不违法——到目前为止,我所遇到的编译器都不会因为你这样做而生成错误。

#define true false

#定义某些关键字可能会因其他原因在编译时生成错误。但其中很多只会导致非常奇怪的程序行为。


这种定义很可能会在标准库中引起问题。 - James Kanze
可能在某些部分存在问题。我遇到的问题是,尽管这是非法的,但似乎没有要求编译器发出诊断报告。并且,根据您使用的标准库的哪些部分以及在代码中放置的#define的位置,程序可能会成功编译。 - Tom Tanner
2
就像很多事情一样,这是未定义的行为。编译器很容易检测到它,并导致编译时错误,但我不知道是否有任何编译器这样做。 - James Kanze
非法但几乎总是被忽略。 - Tom Tanner

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