C 预处理器宏参数在末尾带有空格以进行连接?

Posted

技术标签:

【中文标题】C 预处理器宏参数在末尾带有空格以进行连接?【英文标题】:C preprocessor macro arguments with space at the end for concatenating? 【发布时间】:2016-05-26 12:41:49 【问题描述】:

根据 C11 标准,类函数宏的参数可以在其预处理标记序列的开头或结尾包含空格。对于正常替换,这种空格没有影响。对于字符串化,应忽略空格,如 6.10.3.2.2 中所述

删除第一个预处理标记之前和组成参数的最后一个预处理标记之后的空白。

(我相信这也是证明参数周围可以有空格的证据。)问题是,对于将由 ## 运算符连接的参数,编译器应该如何处理其空格?

我用VC++试了一下,好像忽略了空格。

我认为编译器应该使用空格执行连接。这可能会导致令牌无效,例如由标识符 ABC 和一个以空格作为第一个令牌的参数组成的“ABC”。根据标准,如果以这种方式形成了无效的令牌,则行为未定义。

那么对于前面提到的VC++所做的事情,是简单地忽略空格的结果还是未定义行为的结果?

【问题讨论】:

【参考方案1】:

我认为这也是证明论点周围可以有空格的证据

不,实际上它只是强化了标准的规定,即每个宏参数都是一个预处理标记序列(C2011,6.10.3/11)。源文件中的空格分隔预处理标记;空格的运行本身并不预处理令牌。

您引用的标准部分可能会造成混淆,因为它混合了级别——源文件的字符序列、空格所属的字符序列,以及由源序列的初始标记化产生的预处理标记序列。实际上,字符串化对源字符序列中的相邻标记是否由空格分隔很敏感,但任何此类空格的细节都无关紧要——在字符串化时,被空格分隔的相邻标记由单个空格分隔结果字符串中的字符。

这并不意味着预处理标记可以以空格开头或结尾。它不能;详细信息请参见标准的第 6.4 节。给定的实现如何满足字符串化的规范必然是特定于实现的,但是实现可以这样做的一种方法是为每个预处理标记维护布尔标志,描述该标记在源序列中是否在源序列中前面和/或后面有空格。这些细节与解释标准为结果指定的内容无关,但是,对于字符串化操作符和标记粘贴操作符都没有。

对于将由## 运算符连接的参数,编译器应该如何处理其空格?

## 运算符(或# 运算符)开始发挥作用时,编译器已经完成了它曾经(直接)对源文件中出现的空格字符所做的一切,通过在标记化期间考虑它们源转换为预处理令牌。宏参数是预处理标记的序列,并且仅在这些标记可能是字符串或字符文字或标题名称的范围内,它们可能包含空格。此外,标准规定:

如果在类函数宏的替换列表中,某个参数紧跟在## 预处理标记之前或之后,则该参数将替换为相应参数的预处理标记序列 em> [...]

(C2011,6.10.3.3/2;已添加重点)

再一次,空白运行不是预处理标记。宏扩展和### 运算符处理预处理令牌序列并在其级别操作。空白仅在令牌内部表示在此级别。非预处理标记内部的源文件中的空白仅在预处理标记序列中间接且不确定地表示。

【讨论】:

我明白了。非常感谢您的回答。但我想知道,如果编译器在其参数的预处理标记序列中不包含空格,它将如何进行字符串化?我的意思是,它应该如何知道在相邻预处理标记之间放置空白的位置以及不放置的位置。我猜它不仅记录令牌序列,还记录有关空格的信息?标准对此有何规定? @WuZhenwei,你是对的,字符串化不会在源字符序列中没有任何预处理标记之间插入空格。因为预处理器不关心有多少空格或这些地方的特定空格字符是什么,它可以逐个标记地跟踪标记是否在空格之前和/或之后.这是一个实现细节。我将更新我的答案以澄清。【参考方案2】:

6.10.3.3/3

在替换列表之前 重新检查要替换的更多宏名称,## 预处理标记的每个实例 在替换列表中(不是来自参数)被删除并且前面的预处理 令牌与以下预处理令牌连接。

所以## 连接了两个预处理标记。取自它们的参数是否包含空格无关紧要。

【讨论】:

你的意思是VC++忽略空间是一种未定义的行为? (见我问题的最后一句话。) @WuZhenwei 不,我不是那个意思。您的最后一句话基于一个无效的前提,即预处理器应连接空格。标准中没有任何规定。 谢谢。我认为空白也可能是预处理标记。

以上是关于C 预处理器宏参数在末尾带有空格以进行连接?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Rust 的 FFI 中使用 C 预处理器宏?

如何将带有空格的路径作为参数添加到 CreateProcess 批处理文件?

Doxygen C预处理器宏文档样式

Xcode:测试与调试预处理器宏

标准预处理器宏

用引号定义C ++预处理器宏[重复]