C编程:如何为Unicode编程?

pri*_*bel 80 c unicode locale utf-8 character-encoding

严格的Unicode编程需要哪些先决条件?

这是否意味着我的代码不应该char在任何地方使用类型,并且需要使用可以处理的函数wint_twchar_t

在这种情况下,多字节字符序列的作用是什么?

Jon*_*ler 37

C99或更早

C标准(C99)提供宽字符和多字节字符,但由于无法保证这些宽字符可以容纳的内容,因此它们的价值有限.对于给定的实现,它们提供了有用的支持,但是如果您的代码必须能够在实现之间移动,则不足以保证它们将是有用的.

因此,Hans van Eck建议的方法(即编写ICU的包装器 - 用于Unicode的国际组件 - 库)是合理的,IMO.

UTF-8编码有许多优点,其中之一就是如果你不弄乱数据(例如通过截断它),那么它可以被不完全了解UTF-8错综复杂的函数复制.编码.绝对不是这样的wchar_t.

完整的Unicode是21位格式.也就是说,Unicode保留了从U + 0000到U + 10FFFF的代码点.

关于UTF-8,UTF-16和UTF-32格式(其中UTF代表Unicode转换格式 - 参见Unicode)的一个有用的事情是,您可以在三种表示之间进行转换而不会丢失信息.每个都可以代表其他人可以代表的任何东 UTF-8和UTF-16都是多字节格式.

众所周知,UTF-8是一种多字节格式,其结构非常谨慎,可以从字符串中的任何一点开始可靠地找到字符串中字符的开头.单字节字符的高位设置为零.多字节字符的第一个字符以位模式110,1110或11110中的一个开头(对于2字节,3字节或4字节字符),后续字节始终从10开始.连续字符始终在范围0x80 .. 0xBF.有规则必须以尽可能小的格式表示UTF-8字符.这些规则的一个结果是字节0xC0和0xC1(也是0xF5..0xFF)不能出现在有效的UTF-8数据中.

 U+0000 ..   U+007F  1 byte   0xxx xxxx
 U+0080 ..   U+07FF  2 bytes  110x xxxx   10xx xxxx
 U+0800 ..   U+FFFF  3 bytes  1110 xxxx   10xx xxxx   10xx xxxx
U+10000 .. U+10FFFF  4 bytes  1111 0xxx   10xx xxxx   10xx xxxx   10xx xxxx
Run Code Online (Sandbox Code Playgroud)

最初,希望Unicode是一个16位代码集,一切都适合16位代码空间.不幸的是,现实世界更复杂,必须扩展到当前的21位编码.

因此,UTF-16是用于'基本多语言平面'的单个单元(16位字)代码集,意味着具有Unicode代码的字符指向U + 0000 .. U + FFFF,但是使用两个单位(32位)用于超出此范围的字符.因此,使用UTF-16编码的代码必须能够处理可变宽度编码,就像UTF-8必须一样.双单元字符的代码称为代理.

代理是来自两个特殊范围的Unicode值的代码点,保留用作UTF-16中成对代码单元的前导和尾随值.领先的,也称为高的代理人是从U + D800到U + DBFF,而尾随或低代理是从U + DC00到U + DFFF.它们被称为代理,因为它们不直接表示字符,而只是作为一对字符.

当然,UTF-32可以在单个存储单元中编码任何Unicode代码点.它对计算有效,但对存储无效.

您可以在ICU和Unicode网站上找到更多信息.

C11和 <uchar.h>

C11标准改变了规则,但即使是现在(2017年中),并非所有实施都赶上了变化.C11标准总结了Unicode支持的更改:

  • Unicode字符和字符串(<uchar.h>)(最初在ISO/IEC TR 19769:2004中指定)

以下是功能的最小概述.规范包括:

6.4.3通用字符名称

语法
universal-character-name:
    \u hex-quad
    \U hex-quad hex-quad
hex-quad:
    十六进制数字十六进制数字十六进制数字十六进制数字

7.28 Unicode实用程序 <uchar.h>

标头<uchar.h>声明了用于操作Unicode字符的类型和函数.

声明的类型mbstate_t(在7.29.1中描述)和size_t(在7.19中描述);

char16_t
Run Code Online (Sandbox Code Playgroud)

这是一个用于16位字符的无符号整数类型,与uint_least16_t(在7.20.1.2中描述)的类型相同; 和

char32_t
Run Code Online (Sandbox Code Playgroud)

这是一个用于32位字符的无符号整数类型,与类型相同uint_least32_t(也在7.20.1.2中描述).

(翻译交叉引用:<stddef.h>定义size_t, <wchar.h>定义mbstate_t<stdint.h>定义uint_least16_tuint_least32_t.)<uchar.h>标题还定义了一组(可重新启动的)转换函数:

  • mbrtoc16()
  • c16rtomb()
  • mbrtoc32()
  • c32rtomb()

有关使用\unnnn\U00nnnnnn符号在标识符中使用哪些Unicode字符的规则.您可能必须在标识符中主动激活对此类字符的支持.例如,GCC要求-fextended-identifiers在标识符中允许这些.

请注意,macOS Sierra(10.12.5),但仅限于一个平台,不支持<uchar.h>.

  • 我想你在这里卖的是`wchar_t`和朋友.这些类型是必不可少的,以便允许C库处理*any*编码(包括非Unicode编码)中的文本.如果没有宽字符类型和函数,C库将需要一组文本处理函数来实现*每个*支持的编码:假设koi8len,koi8tok,koi8printf仅用于KOI-8编码文本,utf8len,utf8tok,utf8printf用于UTF -8文字.相反,我们很幸运只有*一组这些函数(不包括原始的ASCII函数):`wcslen`,`wcstok`和`wprintf`. (3认同)
  • 程序员所需要做的就是使用 C 库字符转换函数(“mbstowcs”等)将任何支持的编码转换为“wchar_t”。一旦采用“wchar_t”格式,程序员就可以使用 C 库提供的单组宽文本处理函数。一个好的 C 库实现几乎支持大多数程序员需要的任何编码(在我的一个系统上,我可以访问 221 种独特的编码)。 (2认同)

Han*_*Eck 21

请注意,这不是关于"严格的unicode编程"本身,而是一些实际经验.

我们在公司所做的是围绕IBM的ICU库创建一个包装器库.包装器库具有UTF-8接口,并在需要调用ICU时转换为UTF-16.在我们的例子中,我们并没有过多担心性能问题.当性能成为问题时,我们还提供了UTF-16接口(使用我们自己的数据类型).

应用程序可以保持原样(使用char),但在某些情况下,他们需要了解某些问题.例如,我们使用一个包装器代替strncpy(),避免切断UTF-8序列.在我们的例子中,这已经足够了,但也可以考虑检查组合字符.我们还有用于计算代码点数量,字形数量等的包装器.

当与其他系统连接时,我们有时需要进行自定义字符组合,因此您可能需要一些灵活性(取决于您的应用程序).

我们不使用wchar_t.使用ICU避免了可移植性方面的意外问题(当然不是其他意外问题:-).

  • @Dan Molding:如果strncpy(),比如一个包含单个中文字符(可能是3个字节)的字符串形成一个2字节的字符串数组,则会创建一个无效的UTF-8序列. (8认同)
  • @DanMoulding:如果你*知道你的目标缓冲区足够大,你可以使用`strcpy`(这对UTF-8来说确实是安全的).使用`strncpy`的人可能会这样做,因为他们*不知道目标缓冲区是否足够大,所以他们想要传递最大数量的字节来复制 - 这可能确实会产生无效的UTF-8序列. (5认同)
  • 有效的UTF-8字节序列永远不会被strncpy截断(截断)。有效的UTF-8序列不得包含任何0x00字节(当然,终止空字节除外)。 (2认同)
  • @Hans van Eck:如果你的包装器将单个 3 字节中文字符复制到 2 字节数组中,那么你要么会截断它并创建一个无效序列,要么会出现未定义的行为。显然,如果要复制数据,目标需要足够大;那不用说了。我的观点是,正确使用“strncpy”对于 UTF-8 来说是完全安全的。 (2认同)

dby*_*ron 10

这个FAQ是一个丰富的信息.在该页面和Joel Spolsky的这篇文章之间,你将有一个良好的开端.

我在此过程中得出的一个结论是:

  • wchar_t在Windows上是16位,但在其他平台上不一定是16位.我认为这是Windows上的一个必要的恶魔,但可能在其他地方可以避免.它在Windows上很重要的原因是你需要它来使用名称中包含非ASCII字符的文件(以及W版本的函数).

  • 请注意,采用wchar_t字符串的Windows API需要UTF-16编码.另请注意,这与UCS-2不同.注意代理对.该测试页面具有启发性测试.

  • 如果你在Windows编程,你不能使用fopen(),fread(),fwrite()等,因为他们只需要char *和不理解UTF-8编码.使可移植性变得痛苦.


seb*_*ien 7

要进行严格的Unicode编程:

  • 仅使用字符串的API也支持Unicode( strlen,strcpy...但他们的同行WideString的wstrlen,wsstrcpy...)
  • 处理文本块时,使用允许存储Unicode字符(utf-7,utf-8,utf-16,ucs-2,...)的编码而不会丢失.
  • 检查您的操作系统默认字符集是否与Unicode兼容(例如:utf-8)
  • 使用Unicode兼容的字体(例如arial_unicode)

多字节字符序列是一种在UTF-16编码(通常使用的编码)之前编码的编码,wchar_t在我看来它只是Windows版本.

我从来没有听说过wint_t.


dan*_*n04 5

最重要的是始终明确区分文本和二进制数据尝试遵循Python 3.x strvs.bytes或 SQL TEXTvs.的模型BLOB

不幸的是,C 通过char同时使用“ASCII 字符”和来混淆这个问题int_least8_t。你会想做类似的事情:

typedef char UTF8; // for code units of UTF-8 strings
typedef unsigned char BYTE; // for binary data
Run Code Online (Sandbox Code Playgroud)

您可能还需要 UTF-16 和 UTF-32 代码单元的 typedef,但这会更复杂,因为wchar_t未定义 的编码。您只需要一个预处理器#if。C 和 C++0x 中一些有用的宏是:

  • __STDC_UTF_16__— 如果已定义,则该类型_Char16_t存在并且为 UTF-16。
  • __STDC_UTF_32__— 如果已定义,则该类型_Char32_t存在并且为 UTF-32。
  • __STDC_ISO_10646__— 如果已定义,则为wchar_tUTF-32。
  • _WIN32— 在 Windows 上,wchar_t是 UTF-16,尽管这违反了标准。
  • WCHAR_MAX— 可用于确定 的大小wchar_t,但不能确定操作系统是否使用它来表示 Unicode。

这是否意味着我的代码不应在任何地方使用 char 类型,并且需要使用可以处理 wint_t 和 wchar_t 的函数?

也可以看看:

不会。UTF-8 是一种完全有效的使用char*字符串的 Unicode 编码。它的优点是,如果您的程序对非 ASCII 字节是透明的(例如,作用于其他字符但不改变地传递其他字符的行结束转换器\r\n,则您根本不需要进行任何更改!

如果您使用 UTF-8,则需要更改char= 字符(例如,不要toupper循环调用)或char= 屏幕列(例如,用于文本换行)的所有假设。

如果您使用 UTF-32,您将拥有固定宽度字符的简单性(但不是固定宽度字形,而是需要更改所有字符串的类型)。

如果您选择 UTF-16,则必须放弃固定宽度字符的假设8 位代码单元的假设,这使得这是单字节编码最困难的升级路径。

我建议积极避免, wchar_t因为它不是跨平台的:有时是 UTF-32,有时是 UTF-16,有时是 Unicode 之前的东亚编码。我建议使用typedefs

更重要的是,避免TCHAR

  • 由于可以对纯文本“char”进行签名,因此使用纯字符作为“UTF8”可能会出现符号扩展问题。UTF8 也使用“unsigned char”——或“uint8_t”。 (2认同)