为什么预处理器禁止宏粘贴奇怪的标记

Why does the preprocessor forbid macro pasting of weird tokens

我正在编写自己的基于 GCC 的 C 预处理器。到目前为止它几乎是相同的,但我认为多余的是对由 ## 的 virue 连接的标记执行任何形式的检查。 所以在我的预处理器手册中,我这样写:

3.5 Concatenation

...

GCC forbids concatenation with two mutually incompatible preprocessing tokens such as "x" and "+" (in any order). That would result in the following error: "pasting "x" and "+" does not give a valid preprocessing token" However this isn't true for this preprocessor - concatenation may occur between any token.

我的推理很简单,如果它扩展为无效代码,那么编译器将产生错误,因此我不必显式处理此类情况,从而使预处理器变慢并增加代码复杂性。如果它生成了有效代码,那么此限制删除只会使其更加灵活(尽管可能在极少数情况下)。

所以我想问一下,为什么实际上会发生这个错误,为什么实际上应用了这个限制,如果我在我的预处理器中取消它,这真的是犯罪吗?

就 ISO C 而言,如果 ## 创建无效令牌,则行为未定义。但是这里有一个有点 st运行ge 的情况,如下所示。 C 的预处理 t运行slation 阶段的输出是预处理标记流(pp-tokens)。这些被转换为标记,然后进行句法和语义分析。现在这里有一条重要的规则:如果 pp-token 没有可以将其转换为 token 的形式,则违反约束。因此,您在没有 ## 运算符帮助的情况下自己编写的垃圾预处理器令牌必须被诊断为错误的词法语法。但是,如果您使用 ## 创建一个错误的预处理令牌,则行为未定义。

注意那里的微妙之处:如果 ## 用于创建错误的 preprocessing 令牌,则其行为是未定义的。粘贴是明确定义的,然后在 pp-tokens 转换为标记的阶段被捕获:从评估 ## 的那一点开始,它是未定义的。

基本上,这是历史性的。 C 预处理器在历史上(可能有些是)独立的程序,其词法分析与下游编译器不同并且更宽松。 C 标准试图以某种方式用具有 t运行 化阶段的单一语言来捕捉这一点,结果有一些怪癖和可能令人惊讶的规范不足的领域。 (例如在预处理 t运行slation 阶段,数字标记(“pp-number”)是一个 st运行ge 词法语法,它允许乱码,例如具有多个浮点数的标记 E 指数。)

现在,回到你的情况。您的文本 C 预处理器实际上并不输出 pp-token 对象;它输出另一个文本流。您可能在内部有 pp-token 对象,但它们在输出时会变平。因此,您可能会想,为什么不允许 ## 运算符只是盲目地将任意两个标记粘合在一起?最终效果就好像这些标记被转储到输出流中而没有任何中间空格。 (这可能就是全部,在支持 ## 和 运行 作为单独程序的早期预处理器中)。

不幸的是,这意味着您的 ## 运算符并非纯粹的 善意 令牌粘贴运算符;它只是一个盲目并置运算符,有时会产生一个标记,当它恰好并置两个将被下游编译器作为一个词法分析的标记时。如果您这样做,最好是诚实并记录下来,而不是将其描述为一种灵活性功能。

另一方面,拒绝 ## 运算符中错误的预处理标记的一个很好的理由是捕捉它无法实现其记录的工作描述的情况:从两个中生成一个标记的要求.这个想法是程序员知道语言规范(程序员和实现之间的契约)并且知道 ## 应该制作一个令牌,并依赖于它。对于这样的程序员,任何涉及错误标记粘贴的情况都是错误的,最好通过诊断来支持该程序员。

GCC 和 GNU CPP 预处理器的维护者可能持这种观点:预处理器不是灵活的文本修改工具,而是支持规范的 C 编程的工具链的一部分。

此外,错误的令牌粘贴作业的未定义行为很容易诊断,那么为什么不诊断呢?标准中缺少这方面的诊断要求看起来像只是一个历史性的让步。它是一种诊断的“唾手可得的果实”。让那些难以诊断或难以诊断或需要 运行 时间惩罚的未定义行为未被诊断。