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

伊甸园

我正在编写自己的基于 GCC 的 C 预处理器。到目前为止,它几乎是相同的,但我认为多余的是对由##. 所以在我的预处理器手册中,我写了这个:

3.5 串联

...

GCC 禁止连接两个互不兼容的预处理标记,例如“x”和“+”(以任何顺序)。这将导致以下错误:“粘贴“x”和“+”没有给出有效的预处理标记”但是对于这个预处理器来说不是真的- 任何标记之间都可能发生连接。

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

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

卡兹

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

请注意其中的微妙之处:##如果用于创建错误的预处理令牌,则其行为未定义粘贴是明确定义的,然后在 pp-tokens 转换为标记的阶段被捕获,情况并非如此:从##评估的那个点开始它是未定义的

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

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

不幸的是,这意味着您的##运营商并非纯粹是真正的令牌粘贴运营商;它只是一个盲目的并列运算符,有时会产生一个标记,当它碰巧将两个标记并置时,下游编译器将在词法上分析为一个标记。如果你这样做,最好是诚实地记录它,而不是将它描绘成一个灵活性特性。

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

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

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

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章