什么构成“有效”C标识符?

在@Zaibis建议(和我自己的答案 有关宏名称的有效字符是什么? ,以及++(和其他unicode字符)在g ++不允许的标识符中 ))…

clang允许很多“疯狂”的角色..虽然我一直在努力找到很多押韵或理由 – 至于为什么有些人被允许(🔴ϟツ⌘☁½),而其他人不是(▶︎∀★©)。

例如,以下所有编译A-OK( clang-700.1.76

 #define 💩 ?: // OK (Pile of poo) #define ■ @end // OK (HALFWIDTH BLACK SQUARE) #define 🅺 @interface // OK (NEGATIVE SQUARED LATIN CAPITAL LETTER K) #define P @protocol // OK (FULLWIDTH LATIN CAPITAL LETTER P) 

但以下都导致相同的编译器错误…

Macro name must be an identifier.

 #define ☎ TEL #define ❌ NO #define ⇧ UP #define 〓 == #define 🍎 APPLE 

clang的文档提到了这个问题 ,只说明……

…支持C99和C ++中的扩展标识符。 此function允许标识符包含某些Unicode字符,如活动语言标准所指定; 这些字符可以使用UTF-8编码直接写入源文件,也可以使用通用字符名称(\ u00E0,\ U000000E0)引用。

所以,我想我在问……什么是“主动语言标准”,我怎样才能找到合法标识符的权威来源。

我创建了以下代码,只是为了看看clang会用它做什么。 在测试的大约63488个可能的标识符中,23个发出警告,9506个生成错误。 这使得将近54,000个有效字符用于标识符。 当然够了,但谁被砍了? 为什么?

正如其他人所提到的, ISO / IEC 9899:2011的附录D列出了对C11中通用字符名称有效的字符的hex值。 (我不打算在这里重复它。)我一直在寻找一个答案,为什么选择这个名单“为什么”。

字符集标准

首先,有两个相关的标准定义了一组字符: ISO / IEC 10646 (定义UCS )和Unicode 。 为了进一步混淆(或简化)事物,它们都定义了相同的字符,因为ISO和Unicode使它们保持同步 。 UCS本质上只是一个将值与一组字符(“repertoire”)相关联的字符映射,而Unicode还提供了进一步的定义,例如如何按字母顺序排序( 整理 )比较字符串,哪些代码点代表“规范等效”字符( 规范化 ),以及如何处理从右到左书写的语言中的字符的双向算法等。

C中的通用字符名称

通用字符名称(UCN)是C99(ISO / IEC 9899:1999)中新增的function。 在“国际标准的基本原理—编程语言— C”( 1999年10月 , 修订版2 )中,目的是“在标识符,字符串文字和字符常量中使用任何’本地’字符,同时保留C“的可移植性目标(第5.2.1节)。 本节继续讨论如何在C中编码这些字符的问题( \U\uforms与多字节字符或本机编码)以及如何处理它的策略模型(第14页,参见PDF第22页)。

合理

我希望1999年的相同“基本原理”文件能够说明为什么每个扩展的字符范围被选为C99的UCN可接受的原因。 理由附件一的全部内容如下:

附件一标识符的通用字符名称(规范性)

C9X的新function。

这不是一个理由。 他们甚至不知道C标准将在哪一年出版,因此它被称为“C9X”。 2003年后来的理性文件稍微有点启发性:

附件D标识符的通用字符名称(规范性)

C99的新function。

目的是与ISO / IEC TR 10176保持同步。

ISO / IEC TR 10176是“编程语言标准准备指南”。 它基本上是编写编程语言标准的人的指南。 它包括在编程语言中使用字符集的指南,以及“用户定义的标识符的推荐扩展指令”(附件A)。 但是,2003年理论文件引用的这句话只是“保持现状的意图”,而不是严格遵守TR 10176的承诺。

有一个公开的ISO / IEC TR 10176:2003字符表 。 字符值参考ISO 10646.该表将来自多种语言的字符范围分类为“大写” Lu ; “小写” Ll ; “数字,十进制数字” Nd ,“标点符号,连接PcPc ; 应该清楚这种分类对编程语言的用途。

一个重要的提醒是TR 10176是技术报告,而不是标准。 我在论坛和与其他编程语言相关的文档中找到了几个传递引用,例如Ada,COBOL和D语言。 大部分讨论是关于这些语言的标准应该遵循TR 10176(不是标准)以及TR 10176落后于ISO 10646更新的抱怨。

也许最具启发性的是文档WG21 / N3146 :“C和C ++扩展标识符字符的建议”。 它首先在2010年向标准机构发表评论,建议对标识符的初始字符进行限制。 它提到了关于C引用TR 10176的类似抱怨,并根据Unicode的标识符和模式语法以及XML的通用语法结构的限制,提出了应该允许哪些字符作为标识符的初始字符的建议。 WG21 / N3146给出了后来出现在C11标准ISO / IEC 9899:2011中的建议措辞。 文档末尾有一个表格,有助于阐明所选的字符范围。

C11中允许和不允许的字符

下面是扩展标识符字符的编译范围列表。 粗体范围是C11( ISO / IEC 9899:2011附录D )中给出的范围。 添加了一些关于未在C11中列出斜体范围的注释(即不允许)。 它们在WG21 / N3146中被标记为Unicode的UAX#31或XML的通用语法结构不允许,或者被其他一些注释禁止。

00A8,00AA,00AD,00AF,00B2-00B5,00C0-00D6,00D8-00F6,00F8-00FF 🙁各种字符,如女性ª和阳性º序数指示,带变音符号的元音,数字字符,如上标数,分数等)

(之前的差距) : UAX31和/或XML都不允许这样做 。 (通常标点符号类型标记为«»,货币符号¥£,数学运算符×÷等)

0100-167F :(拉丁语,希腊语,西里尔语,阿拉伯语,泰语,Ethiopic等 – 其他许多人)

1680 :“Ogham块包含特定于脚本的空间:”

1681-180D 🙁 Ogham,他加禄语,蒙古语等)

180E :“蒙古语块包含特定于脚本的空间”

180F-1FFF :(更多语言…语音,扩展拉丁语和希腊语等)

2000 :启动“General Punctuation”块,但有些是允许的:

200B-200D,202A-202E,203F-2040,2054,2060-206F :(来自“通用标点符号”块的选择)

2070-218F :“上标和下标,货币符号,符号的变音符号,字母符号,数字forms”

2190-245F :“箭头,数学运算符,其他技术,控制图片,光学字符识别”

2460-24FF :“封闭的字母数字”

2500 :开始“盒子绘图,块元素,几何形状”等。

2776-2793 :(一些dingbats和带圆圈的dingbats)

2794-2BFF :(不同的dingbat集,数学符号,箭头,盲文模式等)

2C00-2DFF,2E80-2FFF :“Glagolitic,Latin Extended-C,Coptic,Georgian Supplement,Tifinagh,Ethiopic Extended,Cyrillic Extended-A”(也是CJK激进补充)

3000 :(“CJK符号和标点符号”的开头,允许一些选项)

3004-3007,3021-302F,3031-303F 🙁允许“CJK符号和标点符号”)

3040-D7FF :“平假名,片假名”,更多CJK表意文字,部首等。

D800-F8FF :(这将启动高和低代理区域(编码所需的数字空间)和私人使用 )

F900-FD3D,FD40-FDCF,FDF0-FE44,FE47-FFFD :选自“CJK兼容表意文字”,“阿拉伯语演示表格”等.10000-1FFFD,20000-2FFFD,30000-3FFFD,40000-4FFFD,50000- 5FFFD,60000-6FFFD,70000-7FFFD,80000-8FFFD,90000-9FFFD,A0000-AFFFD,B0000-BFFFD,C0000-CFFFD,D0000-DFFFD,E0000-EFFFD : WG21 / N3146给出了这些最终范围的基本原理:

补充私人使用区从F0000延伸至10FFFF; [AltId]和[XML2008]都不允许该范围内的字符。

另外, [AltId]不允许作为非字符的每个平面的最后两个代码位置,即formsP FFFE或P FFFF的每个位置,对于任何P值。

C11附件D.2中的“最初不允许的字符范围”是0300-036F,1DC0-1DFF,20D0-20FF,FE20-FE2F

将WG21 / N3146放置在C11标准的附录D旁边,可以推断出它们如何排列。 例如,似乎不允许数学运算符和标点符号。 我希望这能说明选择允许的角色的“为什么”或“如何”。


TLDR; 版

  • 法定标识符字符的权威来源是C11标准ISO / IEC 9899:2011 (见附录D)。
  • 该清单基于技术报告ISO / IEC TR 10176 ,但有修改 。

C 2011标准

6.4.2标识符

6.4.2.1总则

3标识符中的每个通用字符名称应指定一个字符,其ISO / IEC 10646中的编码属于D.1中规定的范围之一。 71)初始字符不应是指定编码落入D.2规定范围之一的字符的通用字符名称。 实现可以允许不是基本源字符集的一部分的多字节字符出现在标识符中; 哪些字符及其与通用字符名称的对应关系是实现定义的。


71)在链接器不能接受扩展字符的系统上,可以使用通用字符名称的编码来形成有效的外部标识符。 例如,可以使用一些否则未使用的字符或字符序列来对通用字符名称中的\ u进行编码。 扩展字符可能会产生一个长外部标识符。

附件D.

(规范性附录)

标识符的通用字符名称

1本节列出了标识符中通用字符名称有效的hex代码值。

D.1允许的字符范围

1 00A8,00AA,00AD,00AF,00B2-00B5,00B7-00BA,00BC-00BE,00C0-00D6,00D8-00F6,00F8-00FF

2 0100-167F,1681-180D,180F-1FFF

3 200B-200D,202A-202E,203F-2040,2054,2060-206F

4 2070-218F,2460-24FF,2776-2793,2C00-2DFF,2E80-2FFF

5 3004-3007,3021-302F,3031-303F

6 3040-D7FF

7 F900-FD3D,FD40-FDCF,FDF0-FE44,FE47-FFFD

8 10000-1FFFD,20000-2FFFD,30000-3FFFD,40000-4FFFD,50000-5FFFD,60000-6FFFD,70000-7FFFD,80000-8FFFD,90000-9FFFD,A0000-AFFFD,B0000-BFFFD,C0000-CFFFD,D0000 -DFFFD,E0000-EFFFD

D.2最初不允许的字符范围

1 0300-036F,1DC0-1DFF,20D0-20FF,FE20-FE2F

标识符的语法(包括宏名称)在C2011标准的第6.4.2节中给出,如附录D.1所​​解释。 这些规定认为每个标识符可以包含下划线,大写和小写拉丁字母,十进制数字,构成“通用字符名称”(受限制)的字符序列, 以及实现定义的任何其他字符

通用字符名称(UCN)是类似于Java,Python和其他一些语言提供的Unicode转义序列:它们以反斜杠( \ )开头,后跟uU ,以及四个或八个hex数字,分别。 可以使用的特定hex数字序列存在一些限制,一些是通用的,另一些是特定于标识符上下文的。 但请注意,从语法上讲,UCN规定允许出现在标识符中的唯一附加字符是反斜杠; 可以出现在UCN中的所有其他字符也允许在UCN上下文之外的标识符中。

因此,从语法上讲,将讨论限制在标准中要求允许的字符,下划线,(非重音)拉丁字母,十进制数字和反斜杠是C要求必须在标识符中支持的唯一字符。 仅在UCN的上下文中需要支持反斜杠,并且标识符中不允许所有有效的UCN。 此外,该标准不需要支持数字作为标识符的第一个字符。

另一方面,标准在允许标识符中包含“其他实现定义的字符”方面非常宽松,包括作为第一个字符。 即使十进制数字,否则不能是标识符中的第一个字符,原则上可以根据本规定在该位置被允许,由执行决定。 如果您希望代码在实现之间可移植,那么您将避免在任何地方依赖此规定。 如果您想知道您的特定实现允许哪些字符,那么您必须查阅其文档。

每个符合标准的实现必须记录其标准声明为实现定义的每个细节的行为。 例如, GCC的文档指定在大多数目标体系结构的标识符中允许使用美元符号( $ )。 您自己链接并引用了Clang的相同实现定义细节的文档,这更加自由 – 它允许通过UCN在标识符中表示的所有字符也可以由UTF-8字节序列表示。 在许多情况下,如果显示或打印包含此类字节序列的源代码,它们将呈现为单个显示字符。

如前所述, C11标准定义了几个允许的Unicode字符范围。

  • 00A8,00AA,00AD,00AF,00B2-00B5,00B7-00BA,00BC-00BE,00C0-00D6,00D8-00F6,00F8-00FF
  • 0100-167F,1681-180D,180F-1FFF
  • 200B-200D,202A-202E,203F-2040,2054,2060-206F
  • 2070-218F,2460-24FF,2776-2793,2C00-2DFF,2E80-2FFF
  • 3004-3007,3021-302F,3031-303F
  • 3040-D7FF
  • F900-FD3D,FD40-FDCF,FDF0-FE44,FE47-FFFD
  • 10000-1FFFD,20000-2FFFD,30000-3FFFD,40000-4FFFD,50000-5FFFD,60000-6FFFD,70000-7FFFD,80000-8FFFD,90000-9FFFD,A0000-AFFFD,B0000-BFFFD,C0000-CFFFD,D0000- DFFFD,E0000-EFFFD

这也意味着从使用中排除了几个字符范围。
从你的例子:

  • ☎是260E ,来自“其他符号”块: 2600-26FF ,这意味着你错过了所有这些
  • ❌是274C ,来自“Dingbats”区块: 2700-27BF , 所有这些都是允许的( 2776−2793
  • ⇧是21E7 ,来自“箭头”区块: 2190-21FF ,这意味着你错过了所有这些
  • 〓是3013并且来自“CJK符号和标点符号”块: 3000-303F 这些都是允许的,但是其中一些是允许的。
  • 🍎是1F34E并且来自“杂项符号和象形文字”块: 1F300-1F5FF这些都是这些并且实际上应该工作
    (也许是一个clang问题?顺便说一句,这不会显示在我的家用电脑(Ubuntu)上,而是显示在我的工作电脑(Win7)上)