为啥 C11 或 C++11 中没有 ASCII 或 UTF-8 字符文字?

Posted

技术标签:

【中文标题】为啥 C11 或 C++11 中没有 ASCII 或 UTF-8 字符文字?【英文标题】:Why is there no ASCII or UTF-8 character literal in C11 or C++11?为什么 C11 或 C++11 中没有 ASCII 或 UTF-8 字符文字? 【发布时间】:2012-06-11 21:22:48 【问题描述】:

为什么即使有 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 个字符,这还不错,但仍然......

【问题讨论】:

既然是可变宽度编码,你可以把它存储在什么地方? @JoBates 您应该问另一个问题,询问“我怎样才能获得有保证的 ASCII 字符串?”放弃使用 utf8 到达那里的想法。 你可以像这样得到你的 utf-8 字符文字:char c = u8"A"[0]; @DietrichEpp 这是一个常量表达式,因此在 C++11 中,您实际上可以将它用作 switch 语句中的 case (case u8"A"[0]:)。 哦,或者更好:*u8"A"。这也是一个常量表达式。 【参考方案1】:

UTF-8 字符文字必须具有可变长度 - 对于 many 中的大多数,不可能将单个字符存储在 charwchar 中,它应该有什么类型,然后?由于我们在 C 和 C++ 中没有可变长度类型,除了固定大小类型的数组,唯一合理的类型是 const char * - 并且 C 字符串需要以空值结尾,所以它不会不要改变任何东西。

至于编辑:

引用自 C++11 标准:

基本源字符集成员的字形旨在识别对应于 ASCII 字符集的 ISO/IEC 10646 子集中的字符。但是,由于从源文件字符到源字符集的映射(在翻译阶段 1 中描述)被指定为实现定义,因此需要一个实现来记录基本源字符在源文件中的表示方式。

(2.3.1 的脚注)。

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

【讨论】:

我明白这一点,但是对于那些合适的,即使几乎每个(每个?)编译器都可以保证你得到 ASCII/UTF-8 编码会很方便。跨度> 这真的有用吗?这只有在你只做 ASCII 时才有用。 等等。 wchar_tL'0' 呢?它在任何编译器上正好是 0x30 0x00。 @Forgottn:嗯,在大多数计算机上它是 0x30(不是 0x00),但不能保证。它是 16 位还是 32 位,具体取决于,这不是很有用。 @rmartinhofernandes 是的,但不一定保证字符文字可以转换为 ASCII。但是,可以使用仅包含 ASCII 字符的 UTF-8 字符串文字来保证 ASCII 字符串。【参考方案2】:

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

【讨论】:

OP 要求推理,而不是手动实现此类保证的命题... @Griwes 这是一个合理的观点——这是有原因的:为你已经可以做的事情添加一个新的语法是矫枉过正的(使用我上面给出的 static_cast,或者只是 char(30) if你不想输入那么多)。 它将极大地增加可读性。有了这个逻辑,为什么还有字符文字? 您可能会认为编码无关紧要,只要它在为同一平台编写的程序之间保持一致,但我们的计算机今天已经高度联网。如果它们不是保证编码的u8"string" 字面量,我就不会那么烦了。但是,很明显,既然存在这些,任何符合标准的编译器都已经具备将源字符映射到 UTF-8 以及 ASCII 字符的逻辑。【参考方案3】:

编写不可移植的 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"..." 存在?也许我稍后会写一个。 @JoBates 它们被要求分别是char16_tchar32_t 的数组。标准只是缺少调用它们,例如“UTF-16 编码字符串”,而他们确实提到了“UTF-8 编码字符串”。请记住,此类数组的元素 Unicode 代码单元,并且 C++11 标准提供了与所谓的“UTF-16 多字节序列”相互转换的工具。我不知道成为 UTF-16 或 UTF-32 编码的字符串需要什么(也许标准也不知道),但我知道我可以用 U"" 做什么。 @LucDanton 我刚刚在 C++11 标准中注意到了这一点(不在 C11 中):"包含单个 c-char 的 char16_t 文字的值等于其 ISO 10646 代码点值,前提是代码点可以用单个 16 位代码单元表示...包含单个 c-char 的 char32_t 文字的值等于其 ISO 10646 代码点值。”意思是我可以写类似char c = u'0' 的东西,从而保证c == 0x30?如果是这样的话,那么我猜不包括 ASCII char 文字背后的逻辑与不提供明确的短 int 文字是一样的。【参考方案4】:

如您所知,UTF-8 编码的字符需要多个八位字节,即chars,因此它们的自然类型是char[],这确实是带有u8 前缀的字符串文字的类型!所以C11在这里是正确的,只是它坚持使用"作为string的语法约定,需要用作char数组,而不是你隐含的基于语义的提议请改用'

关于"0"u8"0",您没看错,只有后者保证与 0x30, 0 相同,即使在EBCDIC 系统上也是如此。顺便说一句,如果您注意 __STDC_MB_MIGHT_NEQ_WC__ 预定义的标识符,则可以在您的代码中方便地处理前者。

【讨论】:

【参考方案5】:

对于 C++,Evolution Working Group issue 119: Adding u8 character literals 已经解决了这个问题,其 动机 部分说:

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

EWG 讨论了在 Rapperswil 中添加 u8 字符文字的想法并接受了更改。本文为此提供了措辞 扩展名。

这是使用N4267: Adding u8 character literals 中的措辞并入工作​​草案的,我们可以在此时最新的草案标准N4527 中找到措辞,并注意2.14.3 部分说它们仅限于适合的代码点单个 UTF-8 代码单元:

以 u8 开头的字符文字,例如 u8'w',是一个字符 char 类型的文字,称为 UTF-8 字符文字。的价值 UTF-8 字符文字等于其 ISO10646 代码点值, 前提是代码点值可以用单个 UTF-8 代码单元(即,如果它是 US-ASCII 字符)。一种 包含多个 c 字符的 UTF-8 字符文字格式不正确。

【讨论】:

以上是关于为啥 C11 或 C++11 中没有 ASCII 或 UTF-8 字符文字?的主要内容,如果未能解决你的问题,请参考以下文章

c_cpp 仅ASCII ctype.h实现(C11 / C ++ 11 char32_t)。

C11性能之道:转移和转发

对于二维矩阵,它在 C++11 中是不是与 C11 中的函数相似?

GNU C编译器的gnu11和c11

匿名结构和联合何时在 C11 中有用?

C11 多线程有关问题?实在是纠结了很久!