有效和无效 pp-token 的定义是什么?
What are the definitions for valid and invalid pp-tokens?
我想广泛使用 ## 运算符和枚举魔法来处理大量类似的访问操作、错误处理和数据流。
如果应用 ##
和 #
预处理器运算符导致无效的 pp-token,则该行为在 C 中未定义。
C90 中未定义 (*) 预处理器操作的一般顺序(参见 The token pasting operator)。现在在某些情况下,多个 ##/#-Operators 的顺序会影响未定义行为的发生(在不同的来源中这样说,包括 MISRA 委员会和参考页面)。但是我真的很难理解这些来源的例子并确定通用规则。
所以我的问题是:
有效 pp-token 的规则是什么?
不同的 C 和 C++ 标准之间有区别吗?
我当前的问题:以下所有 2 个操作员指令是否合法?(**)
#define test(A) test_## A ## _THING
int test(0001) = 2;
评论:
(*) 我不使用 "is undefined" 因为这与尚未定义的行为无关,恕我直言,而是未指定的行为。应用多个## 或# 运算符通常不会使程序出错。显然有一个顺序——我们只是无法预测是哪个——所以这个顺序是未指定的。
(**) 这不是编号的实际应用。但是模式是等价的。
有效 pp-tokens 的规则是什么?
这些在各自的标准中都有规定; C11 §6.4 和 C++11 §2.4。在这两种情况下,它们都对应于生产 preprocessing-token。除了 pp-number,它们应该不会太令人惊讶。剩下的可能性是标识符(包括关键字)、“标点符号”(在 C++ 中,preprocessing-op-or-punc)、字符串和字符文字,以及任何单个非空白字符'匹配任何其他产品。
除了少数例外,任何字符序列都可以分解为一系列有效的预处理标记。 (一个例外是不匹配的引号和撇号:单引号或撇号不是有效的 预处理标记 ,因此无法标记包含未终止字符串或字符文字的文本。)
但是,在 ##
运算符的上下文中,串联的结果必须是单个 preprocessing-token。因此,无效串联是一种串联,其结果是包含多个 preprocessing-tokens.
的字符序列
C 和 C++ 之间有区别吗?
是的,略有不同:
C++ 具有用户定义的字符串和字符文字,并允许“原始”字符串文字。这些文字将在 C 中以不同方式标记化,因此它们可能是多个 preprocessing-tokens 或者(在原始字符串文字的情况下)甚至无效 preprocessing-tokens.
C++ 包括符号 ::
、.*
和 ->*
,所有这些都将标记为两个 标点符号 标记在 C 中。此外,在 C++ 中,一些看起来像关键字的东西(例如 new
、delete
)是 preprocessing-op-or-punc[=131= 的一部分](尽管这些符号在两种语言中都是有效的 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
是两个token,不能拼接。
这是一个连接顺序很重要的例子:
concat3(27e, -, 7)
如果这是从左到右连接的,它将导致 27e- ## 7
,这是两个 pp 数字的连接。但是 -
不能与 7
连接,因为(如上)-7
不是单个标记。
pp-number到底是什么?
一般而言,pp-number 是标记的超集,可能会转换为(单个)数字文字或可能无效。该定义有意宽泛,部分是为了允许(某些)标记连接,部分是为了使预处理器免受数字格式的周期性变化的影响。准确的定义可以在各自的标准中找到,但通俗地讲,令牌是 pp-number if:
- 它以十进制数字或句点 (
.
) 开头,后跟十进制数字。
- 令牌的其余部分是字母、数字和句点,如果前面有指数符号,则可能包括符号字符(
+
、-
)。两种语言的指数符号都可以是E
或e
;以及自 C99 以来 C 中的 P
和 p
。
- 在 C++ 中,pp-number 还可以包含(但不是开头)撇号后跟字母或数字。
- 注:以上,
letter
包含下划线。此外,可以使用通用字符名称(C++ 中的撇号除外)。
一旦预处理终止,所有 pp-numbers 将尽可能转换为数字文字。如果无法转换(因为令牌不对应于任何数字文字的语法),则该程序无效。
#define test(A) test_## A ## _THING
int test(0001) = 2;
这对于 LTR 和 RTL 评估都是合法的,因为 test_0001
和 0001_THING
都是有效的 preprocessor-tokens。前者是一个标识符,而后者是一个pp-number; pp-number 直到编译的后期阶段才会检查后缀的正确性;想例如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++ 在这方面没有显着差异;这两个标准使用相同的语言(直到 headers 部分)来描述宏替换的过程。语言影响过程的唯一方式是在有效 preprocessing-tokens 中:C++(尤其是最近)有更多形式的有效 preprocessing-tokens,例如 user-defined 字符串文字。
我想广泛使用 ## 运算符和枚举魔法来处理大量类似的访问操作、错误处理和数据流。
如果应用 ##
和 #
预处理器运算符导致无效的 pp-token,则该行为在 C 中未定义。
C90 中未定义 (*) 预处理器操作的一般顺序(参见 The token pasting operator)。现在在某些情况下,多个 ##/#-Operators 的顺序会影响未定义行为的发生(在不同的来源中这样说,包括 MISRA 委员会和参考页面)。但是我真的很难理解这些来源的例子并确定通用规则。
所以我的问题是:
有效 pp-token 的规则是什么?
不同的 C 和 C++ 标准之间有区别吗?
我当前的问题:以下所有 2 个操作员指令是否合法?(**)
#define test(A) test_## A ## _THING int test(0001) = 2;
评论:
(*) 我不使用 "is undefined" 因为这与尚未定义的行为无关,恕我直言,而是未指定的行为。应用多个## 或# 运算符通常不会使程序出错。显然有一个顺序——我们只是无法预测是哪个——所以这个顺序是未指定的。
(**) 这不是编号的实际应用。但是模式是等价的。
有效 pp-tokens 的规则是什么?
这些在各自的标准中都有规定; C11 §6.4 和 C++11 §2.4。在这两种情况下,它们都对应于生产 preprocessing-token。除了 pp-number,它们应该不会太令人惊讶。剩下的可能性是标识符(包括关键字)、“标点符号”(在 C++ 中,preprocessing-op-or-punc)、字符串和字符文字,以及任何单个非空白字符'匹配任何其他产品。
除了少数例外,任何字符序列都可以分解为一系列有效的预处理标记。 (一个例外是不匹配的引号和撇号:单引号或撇号不是有效的 预处理标记 ,因此无法标记包含未终止字符串或字符文字的文本。)
但是,在 ##
运算符的上下文中,串联的结果必须是单个 preprocessing-token。因此,无效串联是一种串联,其结果是包含多个 preprocessing-tokens.
C 和 C++ 之间有区别吗?
是的,略有不同:
C++ 具有用户定义的字符串和字符文字,并允许“原始”字符串文字。这些文字将在 C 中以不同方式标记化,因此它们可能是多个 preprocessing-tokens 或者(在原始字符串文字的情况下)甚至无效 preprocessing-tokens.
C++ 包括符号
::
、.*
和->*
,所有这些都将标记为两个 标点符号 标记在 C 中。此外,在 C++ 中,一些看起来像关键字的东西(例如new
、delete
)是 preprocessing-op-or-punc[=131= 的一部分](尽管这些符号在两种语言中都是有效的 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
是两个token,不能拼接。
这是一个连接顺序很重要的例子:
concat3(27e, -, 7)
如果这是从左到右连接的,它将导致 27e- ## 7
,这是两个 pp 数字的连接。但是 -
不能与 7
连接,因为(如上)-7
不是单个标记。
pp-number到底是什么?
一般而言,pp-number 是标记的超集,可能会转换为(单个)数字文字或可能无效。该定义有意宽泛,部分是为了允许(某些)标记连接,部分是为了使预处理器免受数字格式的周期性变化的影响。准确的定义可以在各自的标准中找到,但通俗地讲,令牌是 pp-number if:
- 它以十进制数字或句点 (
.
) 开头,后跟十进制数字。 - 令牌的其余部分是字母、数字和句点,如果前面有指数符号,则可能包括符号字符(
+
、-
)。两种语言的指数符号都可以是E
或e
;以及自 C99 以来 C 中的P
和p
。 - 在 C++ 中,pp-number 还可以包含(但不是开头)撇号后跟字母或数字。
- 注:以上,
letter
包含下划线。此外,可以使用通用字符名称(C++ 中的撇号除外)。
一旦预处理终止,所有 pp-numbers 将尽可能转换为数字文字。如果无法转换(因为令牌不对应于任何数字文字的语法),则该程序无效。
#define test(A) test_## A ## _THING
int test(0001) = 2;
这对于 LTR 和 RTL 评估都是合法的,因为 test_0001
和 0001_THING
都是有效的 preprocessor-tokens。前者是一个标识符,而后者是一个pp-number; pp-number 直到编译的后期阶段才会检查后缀的正确性;想例如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++ 在这方面没有显着差异;这两个标准使用相同的语言(直到 headers 部分)来描述宏替换的过程。语言影响过程的唯一方式是在有效 preprocessing-tokens 中:C++(尤其是最近)有更多形式的有效 preprocessing-tokens,例如 user-defined 字符串文字。