C++ preprocessor #define-ing a keyword. Is it standards conforming?
在这个关于bool和1的问题的评论中帮助解决正在进行的辩论:
符合标准的C ++预处理器是否允许使用#define重新定义语言关键字? 如果是这样,符合标准的C ++预处理器是否允许这样做?
如果C ++程序重新定义了一个语言关键字,那么该程序本身是否符合标准?
-
我很确定我已经看到了最后一点在这里辩论,但就C99而言。
在C ++中,最接近禁止#define关键字的是§17.4.3.1.1/ 2,它只允许在包含标准库头的翻译单元中使用它:
A translation unit that includes a header shall not contain any macros that define names declared or defined in that header. Nor shall such a translation unit define macros for names lexically identical to keywords.
该段落的第二句在C ++ 0x中被改为彻底禁止#define一个关键字(C ++ 0xFCD§17.6.3.3.1):
A translation unit shall not #define or #undef names lexically identical to keywords.
编辑:正如Ken Bloom在回答他的回答中指出的那样,规则在C ++ 0x中没有改变;文本刚刚被重新排列,以迷惑像我这样的人。 :-)
-
从技术上讲,在C ++ 03中,重新定义关键字是合法的,只要你不包含单个标题?我想我会把它解释为"禁止重新定义关键字",就像C ++ 0x所说的那样。 :)
-
这回答了重新定义关键字的程序是否符合标准而不是预处理器的技术能力的问题。
-
@Ken:对。但是,如果一个程序违反了该声明,那么该程序就是格式错误的,因为该规则是可诊断的(即,它没有说"不需要诊断",并且没有说违反它会导致未定义的行为)。因此,我认为至少需要一个符合要求的预处理器来警告您违反了该规则。
-
@jalf,请注意,就像詹姆斯所说,标准中的"标题"仅指标准库标题。您可能仍然乐意包含自己的源文件。请注意,在C ++ 03中,某些关键字不能#define'ed。这些是new和delete。既然C ++ 0x禁止所有这些,我怀疑open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#369也已经解决了。 (虽然我不确定为什么在预处理阶段特别对待它们)。
-
@James C ++ 0x的17.6.3.3.1 / 2表示"翻译单元不得#define或#undef在名词上与词句相同。",从而保留这些名称。并且17.6.3.3/2说"如果一个程序在保留它的上下文中声明或定义一个名称,除了本条款明确允许的,它的行为是未定义的。",所以我认为它是未定义的行为。 C ++ 0x摆脱了"......包括一个标题......",所以它似乎确实有一个不同的规则集。
-
我不认为17.6.3.1旨在将其限制仅限于那些包括标题的程序。这也意味着你可以使用像__foo这样的名字,只要你不包含标题,这肯定不是意图。如果这样的事情是有意的,那么就不会通过这样的介绍性句子来此外,程序可以通过使用语言支持库组件(例外,新的......)来隐式使用库,因此其用法根本不是由#include行的存在来定义的。
-
@James啊,我注意到coppro(又名Sean Hunt)已经发送了一份关于这个的问题报告。 LWG#1236
-
@Johannes:哦;对不起,我忘记了你的评论。我在飞机上看了它们,无法回复。我同意这样的逻辑:我的答案中的解释意味着你可以使用像__foo这样的名字,这肯定是错的。在任何情况下,语言最多都是模棱两可的,尽管显然它不是缺陷。 : - |
从2005-10-19 C ++工作草案开始工作(因为我没有标准的方便):
第16.3节将#define的语法定义为#define identifier replacement-list-newline(类似对象的宏)或以#define identifier lparen开头的几种结构之一(类似函数的宏)。 identifier在2.10节中定义为identifier-nondigit | identifier identifier-nondigit | identifier digit。第2.11节表明在编译的第7阶段(第2.1节)中无条件地将某个标识符列表视为关键字,因此我得出结论,因此在第4阶段(即预处理器扩展)中不对它们进行特殊处理。因此,标准似乎要求预处理器允许您重新定义语言关键字(在第2.11节中列出)。
但是,预处理器有一个自己的关键字,即defined,以及一个预定义宏列表(第16.8节)。第16.8节声明如果重新定义这些行为,则行为是未定义的,但不禁止预处理器将这些行为识别为宏名称。
-
另请注意,标识符true和false(因为还没有"关键字",它们只是"标识符")在宏替换期间会被特别处理。
-
@James:你能指出它所说的标准(或草案)中的部分吗? GCC的实现还声称在C ++模式下运行时特别处理C ++的命名运算符(C中iso646.h中的#defined)。 gcc.gnu.org/onlinedocs/cpp/Macros.html
-
@Ken:"在执行了由于宏扩展和定义的一元运算符而导致的所有替换后,除了true和false之外的所有剩余标识符和关键字都被替换为pp-number 0"(16.1 / 4) 。至于为什么命名运算符是专门处理的,这是因为命名运算符是"运算符或标点符号"预处理标记,而不是"标识符"预处理标记。
-
@James和G ++团队发现有必要让它们被杀死:stackoverflow.com/questions/2419805/
-
@Ken:当我在第一次评论中说"宏观替换"时,我的意思是"条件包含评估"。对于那个很抱歉。
-
@James,所以重新定义16.8中提到的关键字以外的关键字仍然被预处理器使用,确实在第7阶段之前改变了它们的含义,但在评估#if时,在阶段4中忽略了重新定义。
-
有一个脚注指出宏处理过程中没有关键字(第4阶段)。请注意,17.6.3.1.1在C ++ 95和最新的最终委员会草案之间发生了变化,后者禁止重新定义关键字(尽管在非直观的地方)。
-
我认为这是重要的一部分。 17.6.3.3.1(C ++ 0x FCD中)中的语言表示"名称与关键字在词法上相同",因此即使在预处理过程中没有关键字标记,也可能存在其他标记(即标识符,但也包括op-命名运算符的or-puncs,与词典在词法上完全相同(即具有相同的拼写)。因此,预处理器检测和诊断违反规则应该是微不足道的,并且预处理器不需要允许重新定义关键字。
-
C ++ 0x最终委员会草案在open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3092.pdf,供那些在家中关注的人使用。
-
@James:17.6.3.3.1由17.6.3.1限定,其中规定"本节描述了对使用C ++标准库功能的C ++程序的限制。"
-
@Ken:哦,你是对的;我完全错过了。所以,我认为C ++ 03和C ++ 0x毕竟没有真正的不同(我必须更新我的答案以反映这一点)。但是,鉴于预处理器还处理源文件包含,并且鉴于标准库的名称是已知的,预处理器仍然可以相对容易地检测规则是否已被违反。
根据C ++ 11 [macro.names],这是不允许的:
A translation unit shall not #define or #undef names lexically identical to keywords, to the identifiers listed in Table 3, or to the attribute-tokens described in 7.6.
"表3中列出的标识符"是final和override;和属性标记是[[fallthrough]]中的标识符,依此类推。
该条款仍然是最新的标准。