有效和无效的PP代币的定义是什么?

What are the definitions for valid and invalid pp-tokens?

本文关键字:定义 是什么 PP 无效 有效      更新时间:2023-10-16

我想广泛使用##运算符和枚举魔法来处理大量类似的访问操作,错误处理和数据流。

如果应用 ### 预处理器运算符导致 pp 令牌无效,则行为在 C 中未定义。

C90 中通常未定义预处理器操作的顺序 (*((请参阅令牌粘贴运算符(。现在在某些情况下(在不同的来源中这样说,包括MISRA委员会和参考页面(多个##/#-运算符的顺序会影响未定义行为的发生。但我真的很难理解这些来源的例子并确定共同的规则。

所以我的问题是:

  1. 有效PP代币的规则是什么?

  2. 不同的C标准和C++标准之间有区别吗?

  3. 我目前的问题:以下所有 2 个操作员订单是否合法?**)

    #define test(A) test_## A ## _THING
    int test(0001) = 2;
    

评论:

(*( 我不使用"未定义",因为这与未定义的行为无关,恕我直言,而是未指定的行为。应用多个 ## 或 # 运算符通常不会使程序出错。显然有一个顺序 - 我们无法预测哪个 - 所以顺序没有指定。

(**( 这不是编号的实际应用。但模式是等效的。

有效 pp 代币的规则是什么?

这些在各自的标准中都有详细说明;C11 §6.4 和 C++11 §2.4.在这两种情况下,它们都对应于生产预处理令牌。除了pp编号之外,它们不应该太令人惊讶。其余的可能性是标识符(包括关键字(,"标点符号"(C++,预处理操作或标点(,字符串和字符文字,以及任何与任何其他生产不匹配的非空格字符。

除了少数例外,任何字符序列都可以分解为一系列有效的预处理令牌。(一个例外是不匹配的引号和撇号:单引号或撇号不是有效的预处理标记,因此包含未终止字符串或字符文本的文本不能标记。

但是,在##运算符的上下文中,串联的结果必须是单个预处理令牌。因此,无效串联是一种串联,其结果是包含多个预处理令牌的字符序列。

C 和 C++ 之间有区别吗?

是的,略有不同:

  • C++具有用户定义的字符串和字符文本,并允许使用"原始"字符串文本。这些文字在 C 中将以不同的方式进行标记化,因此它们可能是多个预处理令牌,或者(在原始字符串文本的情况下(甚至是无效的预处理标记

  • C++包括符号::.*->*,所有这些符号在C中都被标记为两个标点符号。此外,在C++中,一些看起来像关键字的东西(例如。 newdelete ( 是预处理操作或朋克的一部分(尽管这些符号在两种语言中都是有效的预处理标记(。

  • C 允许十六进制浮点文字(例如。 1.1p-3 (,它们在C++中不是有效的预处理令牌

  • C++允许在整数文本中使用撇号作为分隔符(1'000'000'000(。在 C 语言中,这可能会导致不匹配的撇号。

  • 在处理通用字符名称方面存在细微差异(例如。 u0234 (。

  • 在C++中,<::将被标记为<::除非后面跟着:>。(<:::<::>使用最长匹配规则正常标记。在 C 语言中,最长匹配规则没有例外; <::始终使用最长匹配规则进行标记化,因此第一个令牌将始终<:

连接test_0001_THING是否合法,即使连接顺序未指定?

是的,这在两种语言中都是合法的。

    test_ ## 0001 => test_0001             (identifier)
    test_0001 ## _THING => test_0001_THING (identifier)
    0001 ## _THING => 0001_THING           (pp-number)
    test_ ## 0001_THING => test_0001_THING (identifier)

无效令牌串联的示例有哪些?

假设我们有

#define concat3(a, b, c) a ## b ## c

现在,无论连接顺序如何,以下内容都是无效的:

concat3(., ., .)

..不是令牌,即使...是。但是串联必须按某种顺序进行,..将是必要的中间值;由于这不是单个令牌,因此串联将无效。

concat3(27,e,-7)

在这里,-7是两个令牌,因此无法连接。

下面是连接顺序很重要的情况:

concat3(27e, -, 7)

如果这是从左到右连接的,则会产生27e- ## 7,这是两个 pp 数字的串联。但是-不能与7连接,因为(如上所述(-7不是单一的标记。

PP编号到底是什么?

一般而言,pp-number 是标记的超集,可以转换为(单个(数字文字或可能无效。该定义有意宽泛,部分是为了允许(某些(标记串联,部分是为了将预处理器与数字格式的周期性更改隔离开来。确切的定义可以在相应的标准中找到,但在以下情况下,非正式地,令牌是 pp 编号

  • 它以十进制数字或句点(.(开头,后跟十进制数字。
  • 令牌的其余部分是字母、数字和句点,如果前面有指数符号,可能包括符号字符(+-(。指数符号可以用两种语言Ee;并且自 C99 以来也在 C 语言中Pp
  • 在C++中,pp 编号还可以包含(但不以撇号开头(后跟字母或数字。
  • 注意:上面,letter包括下划线。此外,可以使用通用字符名称(除了在 C++ 中跟随撇号(。

一旦预处理终止,所有 pp 数字都将转换为数字文字(如果可能(。如果无法进行转换(因为令牌与任何数字文本的语法不对应(,则程序无效。

#define test(A) test_## A ## _THING
int test(0001) = 2;

这对于 LTR 和 RTL 评估都是合法的,因为 test_00010001_THING 都是有效的预处理器令牌。前者是标识符,而后者是 pp 编号;直到编译的后期阶段才检查 pp 编号的后缀正确性;例如,思考 0001u无符号的八进制文字。

举几个例子来说明评估的顺序确实很重要:

#define paste2(a,b) a##b
#define paste(a,b) paste2(a,b)
#if defined(LTR)
#define paste3(a,b,c) paste(paste(a,b),c)
#elif defined(RTL)
#define paste3(a,b,c) paste(a,paste(b,c))
#else
#define paste3(a,b,c)  a##b##c
#endif
double a = paste3(1,.,e3), b = paste3(1e,+,3);  // OK LTR, invalid RTL
#define stringify2(x) #x
#define stringify(x) stringify2(x)
#define stringify_paste3(a,b,c) stringify(paste3(a,b,c))
char s[] = stringify_paste3(%:,%,:);            // invalid LTR, OK RTL

如果您的编译器使用一致的计算顺序(LTR 或 RTL(,并在生成无效的 pp 令牌时出现错误,那么恰好其中一行将生成错误。当然,宽松的编译器很可能允许两者,而严格的编译器可能两者都不允许。

第二个例子相当人为;由于语法的构造方式,很难找到一个在构建 RTL 时有效但在构建 LTR 时无效的 pp-token 。

在这方面,C 和 C++ 之间没有显着差异;这两个标准具有相同的语言(直到章节标题(描述宏替换过程。语言可能影响该过程的唯一方式是在有效的预处理令牌中:C++(尤其是最近(具有更多形式的有效预处理令牌,例如用户定义的字符串文本。