【问题标题】:What are the definitions for valid and invalid pp-tokens?有效和无效 pp-token 的定义是什么?
【发布时间】:2015-09-28 00:10:34
【问题描述】:

我想广泛使用 ##-operator 和 enum 魔法来处理大量类似的访问操作、错误处理和数据流。

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

C90 中通常没有定义 (*) 预处理器操作的顺序(请参阅 The token pasting operator)。现在在某些情况下(在不同的来源,包括 MISRA 委员会和引用的页面中这样说),多个 ##/#-Operators 的顺序会影响未定义行为的发生。但我很难理解这些来源的示例并确定共同规则。

所以我的问题是:

  1. 有效 pp-token 的规则是什么?

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

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

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

评论:

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

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

【问题讨论】:

标签: c++ c c-preprocessor c89


【解决方案1】:

有效pp-tokens的规则是什么?

这些在各自的标准中都有说明; C11 §6.4 和 C++11 §2.4。在这两种情况下,它们都对应于生产 preprocessing-token。除了 pp-number,它们应该不会太令人惊讶。其余的可能性是标识符(包括关键字)、“标点符号”(在 C++ 中,preprocessing-op-or-punc)、字符串和字符文字以及任何不匹配的单个非空白字符任何其他作品。

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

但是,在## 运算符的上下文中,连接的结果必须是单个预处理令牌。所以一个无效的连接是一个连接,它的结果是一个包含多个预处理标记的字符序列。

C和C++有区别吗?

是的,有细微的差别:

  • C++ 具有用户定义的字符串和字符文字,并允许“原始”字符串文字。这些文字将在 C 中以不同的方式进行标记,因此它们可能是多个 preprocessing-tokens 或(在原始字符串文字的情况下)甚至是无效的 preprocessing-tokens

  • C++ 包括符号 ::.*->*,所有这些符号在 C 中都将被标记为两个 punctuator 标记。此外,在 C++ 中,有些东西看起来像关键字(例如newdelete)是 preprocessing-op-or-punc 的一部分(尽管这些符号在两种语言中都是有效的preprocessing-tokens。)

  • C 允许使用十六进制浮点文字(例如 1.1p-3),这在 C++ 中是无效的preprocessing-tokens

  • 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-number

一般来说,pp-number 是标记的超集,可以转换为(单个)数字文字或可能无效。该定义有意广泛,部分是为了允许(某些)令牌连接,部分是为了使预处理器与数字格式的周期性变化隔离。准确的定义可以在各自的标准中找到,但非正式的令牌是 pp-number 如果:

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

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

【讨论】:

  • 你知道C标准的不同迭代和C++标准的不同迭代之间是否也有区别?
  • @MarkA.:是的,有区别。我提到的所有不兼容性都是在某个时间点以一种或另一种语言添加功能的结果(如果两种语言都存在,则不一定在两种语言中同时添加。)这包括 unicode 字符,原始字符串、十六进制浮点数、撇号分隔符、备用标记(“digraphs”)等。字符串编码前缀集(当前为uu8UL)随着时间的推移而变化。但大体上是一样的。
  • @MarkA。 <: 等有向图不在 C89/90 中,而是在 C99 中添加。
  • @MarkA.:如果您的问题与串联顺序有关,那么答案是否定的。在所有版本的 C 和 C++ 标准中,连接运算符的应用顺序是未指定的,有效的程序不能依赖它。
  • pp-number的概念包括字符序列“E+”、“E-”、“e+”、“e-”有什么问题,解决不了好吧,以“e”或“E”结尾的pp-number必须后跟一串数字,或者是“+”或“-”和一串数字?当然,这会接受“1.23E + 7”作为有效的浮点常量,但是在什么意义上,这比在与 14 mod 16 一致的十六进制常量和后面的 + 或 - 字符之间有一个空格更重要?
【解决方案2】:
#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-token 时出现错误,那么这些行中的某一行将产生错误。自然,宽松的编译器可以同时允许两者,而严格的编译器可能都不允许。

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

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

【讨论】:

  • 10.e 是一个有效的 pp 号;我不明白为什么您的 rtl 示例需要 rtl 评估。
猜你喜欢
  • 1970-01-01
  • 2011-06-22
  • 2015-07-27
  • 1970-01-01
  • 1970-01-01
  • 2015-07-31
  • 2019-09-18
  • 1970-01-01
  • 2020-04-19
相关资源
最近更新 更多