有效和无效pp-tokens的定义是什么?

Mar*_* A. 3 c c++ c89 c-preprocessor

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

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

一般情况下,预处理器操作的顺序未在C90中定义(*)(请参阅令牌粘贴操作符).现在在某些情况下,它发生(在不同来源,包括MISRA委员会,以及引用的页面),多个## /# - 运算符的顺序会影响未定义行为的发生.但是我很难理解这些来源的例子并确定共同规则.

所以我的问题是:

  1. 有效pp-tokens有哪些规则?

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

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

    #define test(A) test_## A ## _THING
    int test(0001) = 2;
    
    Run Code Online (Sandbox Code Playgroud)

评论:

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

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

ric*_*ici 5

有效pp-tokens有哪些规则?

这些都在各自的标准中阐明; C11§6.4和C++11§2.4.在这两种情况下,它们都对应于生产预处理令牌.除了pp-number,它们也不应该太令人惊讶.剩下的可能性是标识符(包括关键字),"标点符号"(在C++中,预处理-op-or-punc),字符串和字符文字,以及任何与其他任何产品都不匹配的单个非空白字符.

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

##但是,在运算符的上下文中,连接的结果必须是单个预处理标记.因此无效的连接是连接,其结果是包含多个预处理标记的字符序列.

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

是的,有一点点差异:

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

  • C++包含符号::,.*并且->*所有这些都将被标记为C中的两个标点符号.此外,在C++中,一些看起来像关键字的东西(例如new,delete)是预处理的一部分- op-or-punc(尽管这些符号是两种语言中有效的preprocssing-tokens.)

  • 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)
Run Code Online (Sandbox Code Playgroud)

无效令牌连接的示例是什么?

假设我们有

#define concat3(a, b, c) a ## b ## c
Run Code Online (Sandbox Code Playgroud)

现在,无论串联顺序如何,以下都是无效的:

concat3(., ., .)
Run Code Online (Sandbox Code Playgroud)

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

concat3(27,e,-7)
Run Code Online (Sandbox Code Playgroud)

这里-7是两个令牌,所以它不能连接.

这是一个连接顺序很重要的情况:

concat3(27e, -, 7)
Run Code Online (Sandbox Code Playgroud)

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

什么是pp数

一般而言,pp-number是令牌的超集,可能会转换为(单个)数字文字或可能无效.该定义有意宽泛,部分是为了允许(某些)令牌连接,部分是为了使预处理器与数字格式的周期性变化隔离.精确定义可以在相应的标准中找到,但非正式地,如果出现以下情况,则令牌是pp编号:

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

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