为什么C11或C++11中没有ASCII或UTF-8字符字面量?

17
为什么即使有UTF-8字符串字面量,仍然没有C11或C++11中的UTF-8字符字面量?我知道一般来说,字符字面量表示一个单个的ASCII字符,它与单个字节UTF-8代码点相同,但是C和C++都没有规定编码必须是ASCII。
基本上,如果我正确阅读标准,那么'0'不一定代表整数0x30,而u8“0”必须代表字符序列0x30 0x00。
注:我知道并非每个UTF-8代码点都适合一个字符,这样的字面量只对单字节代码点(即ASCII)有用,因此称其为“ASCII字符字面量”更合适,所以问题仍然存在。我只是选择用UTF-8来框架问题,因为有UTF-8字符串字面量。唯一想象中可移植保证ASCII值的方法是为每个字符编写一个常量,考虑到只有128个字符,这并不是太糟糕...

5
由于这是一种可变宽度编码,您可以将其存储在哪里? - Pubby
2
@JoBates 你应该再提出一个问题,询问如何获取一个保证是ASCII的字符串?不要考虑使用utf8。 - Pubby
2
@DietrichEpp 这是一个常量表达式,因此在C++11中,您实际上可以将其用作switch语句中的一个case(case u8"A"[0]:)。 - bames53
2
哦,或者更好:*u8"A"。这也是一个常量表达式。 - bames53
2
u8字符字面量现在被考虑用于C++17:https://isocpp.org/files/papers/n4267.html - jbatez
显示剩余6条评论
5个回答

10

写非可移植的C代码是完全可以接受的,这是许多理由之一。可以假设您的系统使用ASCII或其某个超集,并警告用户不要在EBCDIC系统上运行您的程序。

如果您非常慷慨,可以编码一个检查。已知gperf程序会生成包括此类检查的代码。

_Static_assert('0' == 48, "must be ASCII-compatible");

或者,对于C11之前的编译器,
extern int must_be_ascii_compatible['0' == 48 ? 1 : -1];

如果你在使用C11,你可以在字符常量上使用uU前缀,但不能使用u8前缀...

/* This is useless, doesn't do what you want... */
_Static_assert(0, "this code is broken everywhere");
if (c == '々') ...

/* This works as long as wchar_t is UTF-16 or UTF-32 or UCS-2... */
/* Note: you shouldn't be using wchar_t, though... */
_Static_assert(__STDC_ISO_10646__, "wchar_t must be some form of Unicode");
if (c == L'々') ...

/* This works as long as char16_t is UTF-16 or UCS-2... */
_Static_assert(__STDC_UTF_16__, "char16_t must be UTF-16");
if (c == u'々') ...

/* This works as long as char32_t is UTF-32... */
_Static_assert(__STDC_UTF_32__, "char32_t must be UTF-32");
if (c == U'々') ...

有一些项目采用非常可移植的C语言编写,并已被移植到非ASCII系统(例如example)。这需要相当大的移植工作,除非您知道要在EBCDIC系统上运行代码,否则没有真正的理由去做这种努力。
关于标准:编写C标准的人必须处理每个可能的C实现,包括一些非常奇怪的实现。已知存在一些系统,其中sizeof(char) == sizeof(long)CHAR_BIT != 8,整数类型具有陷阱表示,sizeof(void *) != sizeof(int *)sizeof(void *) != sizeof(void (*)())va_list是堆分配等等。这是一场噩梦。
不要试图编写可以在您从未听说过的系统上运行的代码,并且不要过分追求C标准中的保证。
例如,就C标准而言,以下是malloc的有效实现:
void *malloc(void) { return NULL; }

请注意,虽然u8"..."常量保证是UTF-8编码,但u"..."U"..."没有任何保证,除了编码分别为16位和32位每个字符,实际编码必须由实现记录。 摘要:在2012年可以安全地假定ASCII兼容性。

等等,u"..."U"..."不需要是UTF-16和UTF-32吗?我猜u8"..."就是奇怪的那一个。所以,反过来问!为什么会有u8"..."存在呢?也许我稍后会写一篇关于它的文章。 - jbatez
@JoBates,它们分别被规定为char16_tchar32_t数组。标准几乎称它们为“UTF-16编码字符串”,而提到了“UTF-8编码字符串”。请记住,这种数组的元素Unicode代码单元,并且C++11标准提供了将其转换为和从其称为“UTF-16多字节序列”的内容的工具。我不知道成为UTF-16或UTF-32编码字符串需要什么(也许标准也不知道),但我知道我可以用U""做什么。 - Luc Danton
@LucDanton 我刚注意到这个在C++11标准中(而不是C11):“包含单个c-char的char16_t文字的值等于其ISO 10646代码点值,前提是代码点可以用单个16位代码单元表示...包含单个c-char的char32_t文字的值等于其ISO 10646代码点值。”这是否意味着我可以编写类似char c = u'0'的内容,从而保证c == 0x30?如果是这样的话,那么我猜不包括ASCII字符文字的逻辑与不提供显式short int文字相同。 - jbatez

8

UTF-8字符字面量的长度是可变的——对于大多数字符来说,无法存储单个字符于 charwchar 中,那么它应该是什么类型呢?既然 C 和 C++ 都没有可变长度类型,除了固定大小类型的数组外,唯一合理的类型就是 const char * 了,而 C 字符串需要以空字符结尾,因此这并不会改变任何事情。

至于修改:

C++11 标准的引用:

基本源字符集成员的字形用于识别与 ASCII 字符集相对应的 ISO/IEC 10646 的子集中的字符。但是,由于从源文件字符到源字符集的映射(在翻译阶段 1 中描述)被指定为实现定义,因此必须记录实现如何在源文件中表示基本源字符。

(见 2.3.1 脚注)。

我认为这是不保证的充分理由。尽管正如您在此处的评论中指出的那样,对于大多数(或每个)主流编译器来说,字符字面量的 ASCII 特性是实现所保证的。


等等。wchar_tL'0'呢?在任何编译器上,它确实是0x30 0x00。 - Forgottn
1
@Forgottn:嗯,在大多数计算机上是0x30(没有0x00),但不能保证。而且它要么是16位,要么是32位,这并不太有用。 - Dietrich Epp
@Griwes 等等,你是不是认为这个引用意味着字符字面量保证映射到它们的ASCII整数值?“打算”这个词相当模糊。 - jbatez
嗯...@Griwes,请告诉我你将使用哪些奇特的编译器?我认为,至少前五名流行的编译器可以保证这种映射。 - Forgottn
最近添加了这个功能,并且正如我在回答中提到的那样,他们通过使其不适应来解决了尺寸问题。 - Shafik Yaghmour
显示剩余9条评论

6

对于C++,这个问题已经被Evolution Working Group issue 119: Adding u8 character literals解决了,其Motivation部分如下:

我们有五种字符串字面量的编码前缀(无、L、u8、u、U),但只有四种字符字面量的编码前缀——缺少的是u8。如果窄执行字符集不是ASCII,则u8字符字面量将提供一种使用保证ASCII编码的字符字面量的方法(单代码单元u8编码正好是ASCII)。添加对这些字面量的支持将增加一个有用的功能,并使语言略微更加一致。

EWG在Rapperswil讨论了添加u8字符字面量的想法并接受了这个改变。本文提供了该扩展的措辞。

这是使用 N4267:添加u8字符字面量 中的措辞并纳入到工作草案中的,在此最新的标准草案 N4527 中可以找到该措辞,并且请注意,第 2.14.3 节指出它们仅限于适合单个 UTF-8 代码单元的代码点:

以 u8 开头的字符字面量(例如 u8'w')是 char 类型的字符字面量,称为 UTF-8 字符字面量。UTF-8 字符字面量的值等于其 ISO10646 代码点值,前提是该代码点值可用单个 UTF-8 代码单元表示(即,提供它是 US-ASCII 字符)。包含多个 c-char 的 UTF-8 字符字面量是非法的。


0

正如您所知,UTF-8编码的字符需要多个八位字节,因此需要使用char[]类型,这确实是前缀字符串字面量的类型!因此,C11在这里走在了正确的轨道上,只是它坚持使用"作为字符串的语法约定,需要将其用作char数组,而不是您暗示的基于语义的建议,即改用'

关于"0"u8"0",您读得没错,只有后者保证在EBCDIC系统上也与{ 0x30, 0 }完全相同。顺便说一下,如果您注意到预定义标识符__STDC_MB_MIGHT_NEQ_WC__,那么前者不相同的事实可以方便地在您的代码中处理。


0

如果您不相信编译器会将'0'视为ASCII字符0x30,那么您可以使用static_cast<char>(0x30)代替。


2
OP要求的是理由,而不是手动实施这些保证的建议... - Griwes
@Griwes 这是一个合理的观点——这个原因怎么样:为已经可以做到的事情(使用我上面提供的 static_cast,或者只是 char(30) 如果你不想打那么多字)添加新的语法是过度的。 - Edward Loper
2
这将极大地提高可读性。按照这个逻辑,为什么还要有字符字面量呢? - jbatez
你可能会认为编码不重要,只要在同一平台上编写的程序保持一致即可。但是现在我们的计算机高度网络化。如果没有 u8"string" 字面量来保证编码,我可能不会那么烦恼。但是,显然,由于这些存在,任何符合标准的编译器都已经具备将源字符映射到 UTF-8 和 ASCII 字符的逻辑。 - jbatez

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接