【问题标题】:Is it legal to redefine a C++ keyword?重新定义 C++ 关键字是否合法?
【发布时间】:2012-02-24 22:23:51
【问题描述】:

本周大师this article中说:It is illegal to #define a reserved word.这是真的吗?我在规范中找不到任何东西,例如,我已经看到程序员重新定义新事物。

【问题讨论】:

  • 绝对可以使用#define 来更改保留字的含义。事实上,它经常在The International Obfuscated C Code Contest 条目中使用。这是可能的,因为#define 定义的宏在实际 C 编译器运行之前被单独的程序替换。
  • 确定他们没有超载 new 运算符?由于预处理器首先运行 re-“#define”-ing 关键字的问题是您用替换字符串替换关键字,因此之后的代码很可能会被破坏。这样做通常是个坏主意,你为什么还要这样做?
  • @ted:想法是在源文件中重新定义新关键字,以便调用特定于平台的实现:Macintosh 上的#define new newMac 和PC 上的#define new newPc。在其他翻译单元中,相应的函数将定义特定于平台的内存分配器。我想这个想法是继续在任何地方使用new,同时在无法将每个调用重命名为 new 时具有特定于平台的行为。
  • 过去,我们使用#define for if (false) else for 修复了 Visual Studio 6 对在 for 循环中声明的变量的不正确作用域
  • “我已经看到程序员重新定义新”是什么意思?你见过使用#define 的例子吗?还是您指的是重载new 运算符?这是两种截然不同的东西。

标签: c++ standards keyword c-preprocessor


【解决方案1】:

17.4.3.1.1 宏名称 [lib.macro.names]

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

顺便说一下,new 是一个运算符,用户可以通过提供自己的版本来重载(替换)它。

【讨论】:

  • 请注意,该规则仅适用于包含标准标头的源。如果翻译单元不包含标准标题,则重新定义关键字是完全合法的。 (不过,它对代码的可读性影响不大。)
  • 所以只要我的翻译单元不包含标题,我就可以#define new :P ? @JamesKanze:为什么是 standard 标头?
  • @victor: 标准(模板)库??
  • @victor - 该语言允许编译器知道每个标准头文件中的内容,因此不必每次都重新编译它们。因此,您不得通过重新定义这些标题中的某些词来更改它们。你对自己的标题做什么取决于你!
  • 在 C++03 标准中,术语 header17.4.1.2 Headers [lib.headers] 中定义为:“元素C++ 标准库的一部分在 header 中声明或定义(视情况而定)。”这与我们通常认为的标头(.h 文件)不符。该标准没有 .cpp 文件和 .h 文件之间的区别的概念;它们都被定义为源文件
【解决方案2】:

来自 C++11 的相应部分:

17.6.4.3.1 宏名称 [macro.names]

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

C++03 中的第 1 段已被删除。第二段被分成两段。前半部分现已更改为明确声明它仅适用于 standard 标头。第二点已扩大到包括任何翻译单元,而不仅仅是包含标题的那些。

但是,该标准的这一部分的概述17.6.4.1 [constraints.overview])规定:

本节描述了对使用 C++ 标准库设施的 C++ 程序的限制。

因此,如果您不使用 C++ 标准库,那么您可以按照自己的意愿去做。

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

【讨论】:

  • 使用C++标准库的定义是什么?例如,int main() {} 程序是否使用 C++ 标准库?我问这个是因为[basic.start.main]/5 提到了std::exit
【解决方案3】:

他们实际上是错的,或者至少没有说明整个故事。它被禁止的真正原因是它违反了单一定义规则(顺便说一句,这也是它非法的第二个原因)。

要查看它实际上是否允许(重新定义关键字),至少如果您不使用标准库,您必须查看标准的完全不同的部分,即翻译阶段。它说输入仅在预处理发生之前分解为预处理器标记,并且查看privatefubar 之间没有区别的那些,它们都是预处理器的identifiers。稍后当输入分解为token 时,替换已经发生了。

有人指出,对使用标准库的程序存在限制,但重新定义 private 的示例是否正在这样做并不明显(与“第 4 人:语言律师”相反) sn-p 用于输出到cout)。

在上一个例子中提到过这个技巧不会被其他翻译单元践踏或践踏。考虑到这一点,您可能应该考虑在其他地方使用标准库的可能性,这将使该限制生效。

【讨论】:

    【解决方案4】:

    如果您不希望有人使用 goto,您可以执行以下操作。只需将以下内容放在他不会注意到的代码中。

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

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

    【讨论】:

    • @JackAidley 是的,但你有没有问过自己是什么让它变得有趣?答案是人类是虐待狂的生物,这才是最可怕的部分……+1 指出人性。
    • 这是一种未定义的行为,最好避免。
    • 如果你认为goto不好,就这样,你不应该写程序,如果你能说服我绝对不能使用goto,我会很惊讶。
    • @iharob 同意。有限状态机作为goto 的潜在良好用途浮现在脑海中
    • 您关于它总是会使程序崩溃的说法是不正确的。因此,真正的程序员永远不应该这样做。在许多编译器上,该语句将被优化并且程序不会崩溃。编译器可以轻松优化块语句中的所有内容,因为 x 无论如何都未使用。即使没有优化,它仍然是未定义的行为,不能保证崩溃。
    【解决方案5】:

    据我所知,这不是非法的 - 如果你这样做,我遇到的任何编译器都不会产生错误

    #define true false
    

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

    【讨论】:

    • 这样的定义很可能会导致标准库出现问题。
    • 可能,在某些部分。我遇到的问题是,虽然它是非法的,但编译器似乎没有任何要求发出诊断。根据您使用的标准库的哪些位以及将#define 放在代码中的哪个位置,程序可以正常编译。
    • 就像很多事情一样,这是未定义的行为。编译器检测到它并导致编译时错误并不难,但我不知道有什么。
    • 非法但几乎总是被忽略
    猜你喜欢
    • 1970-01-01
    • 2019-01-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-12
    • 2011-07-31
    • 2010-11-08
    • 2013-04-04
    相关资源
    最近更新 更多