C编程:如何编程为Unicode?

严格的Unicode编程需要什么先决条件?

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

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

请注意,这不是关于“严格的Unicode编程”本身,而是一些实际的经验。

我们在公司做的是在IBM的ICU库上创build一个包装库。 包装库有一个UTF-8接口,当有必要调用ICU时,转换为UTF-16。 在我们的例子中,我们并不太在乎性能命中。 当性能出现问题时,我们也提供了UTF-16接口(使用我们自己的数据types)。

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

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

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

C99或更早版本

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

因此,国际海事组织(IMO)Hans van Eck(即在ICU – Unicode国际组件编写的一个封装器)提出的方法是合理的。

UTF-8编码有许多优点,其中之一就是如果你不把数据弄乱(例如截断数据),那么UTF-8编码就可以被那些并不完全知道UTF-8错综复杂的函数所复制编码。 这与wchar_t的情况wchar_t

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

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

众所周知,UTF-8是一种多字节格式,具有谨慎的结构,可以从string中的任意位置开始可靠地查找string中的字符开头。 单字节字符的高位被设置为零。 多字节字符具有以位模式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 

最初希望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网站上find更多信息。

C11和<uchar.h>

C11标准改变了规则,但是并不是所有的实现都已经赶上了变化(即2017年中)。 C11标准总结了Unicode支持的变化:

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

接下来是function的最基本的概要。 规格包括:

6.4.3通用字符名称

句法
通用字符名称:
\u 六angular形
\U hex四方六angular形
六angular四:
hex数字hex数字hex数字hex数字

7.28 Unicode实用程序<uchar.h>

头文件<uchar.h>声明了处理Unicode字符的types和函数。

声明的types是mbstate_t (在7.29.1中描述)和size_t (在7.19中描述);

 char16_t 

它是一个无符号整数types,用于16位字符,与uint_least16_ttypes相同(在7.20.1.2中描述); 和

 char32_t 

这是一个用于32位字符的无符号整数types,与uint_least32_ttypes相同(在7.20.1.2中也有描述)。

(翻译交叉引用: <stddef.h>定义了size_t<wchar.h>定义了mbstate_t<stdint.h>定义了uint_least16_tuint_least32_t 。) <uchar.h>头文件还定义了一个最小集合(可重启)转换function:

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

有关使用\unnnn\U00nnnnnn表示法可以在标识符中使用哪些Unicode字符的规则。 您可能必须主动激活对标识符中这些字符的支持。 例如,GCC需要-fextended-identifiers来允许这些标识符。

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

这个FAQ是丰富的信息。 在这个页面和Joel Spolsky的这篇文章之间,你会有一个好的开始。

我得出的一个结论是:

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

  • 请注意,采用wchar_tstring的Windows API需要UTF-16编码。 还要注意,这与UCS-2不同。 注意代理对。 这个testing页面有启发性testing。

  • 如果你在Windows上编程,你不能使用fopen()fread()fwrite()等等,因为它们只带有char *而不理解UTF-8编码。 使可移植性痛苦。

-D B

要严格的Unicode编程:

  • 只能使用支持Unicode的stringAPI( 不是 strlenstrcpy ,…而是它们的wstrlen wsstrcpy对应wstrlenwsstrcpy ,…)
  • 处理文本块时,请使用允许存储Unicode字符(utf-7,utf-8,utf-16,ucs-2,…)而不会丢失的编码。
  • 检查您的操作系统默认字符集是否兼容Unicode(例如:utf-8)
  • 使用Unicode兼容的字体(例如arial_unicode)

多字节字符序列是在UTF-16编码(通常使用wchar_t )之前进行编码的编码,在我看来,它仅仅是Windows。

我从来没有听说过wint_t

你基本上想要处理内存中的string作为wchar_t数组而不是char。 当你做任何types的I / O时(比如读/写文件),你可以使用UTF-8进行编码/解码(这可能是最常见的编码),这很容易实现。 只需谷歌的RFC。 所以在内存中什么都不应该是多字节的。 一个wchar_t代表一个字符。 但是,当你要序列化的时候,你需要编码UTF-8,其中一些字符由多个字节表示。

你还必须为宽string编写strcmp等的新版本,但这不是一个大问题。 最大的问题是与只接受字符数组的库/现有代码进行交互。

当涉及到sizeof(wchar_t)(如果你想正确的话,你需要4个字节),你可以随时使用typedef /macros来重新定义它,如果你需要的话。

最重要的是要始终明确区分文本和二进制数据 。 尝试遵循Python 3.x strbytes或SQL TEXT vs. BLOB

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

 typedef char UTF8; // for code units of UTF-8 strings typedef unsigned char BYTE; // for binary data 

您可能也希望使用UTF-16和UTF-32代码单元的typedefs,但是这更复杂,因为wchar_t的编码没有定义。 你只需要一个预处理器#if s。 C和C ++ 0x中的一些有用的macros是:

  • __STDC_UTF_16__ – 如果已定义,则types_Char16_t存在且为UTF-16。
  • __STDC_UTF_32__ – 如果已定义,则types_Char32_t存在且为UTF-32。
  • __STDC_ISO_10646__ – 如果已定义,则wchar_t是UTF-32。
  • _WIN32 – 在Windows上, wchar_t是UTF-16,尽pipe这违反了标准。
  • WCHAR_MAX – 可用于确定wchar_t的大小,而不是操作系统是否使用它来表示Unicode。

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

也可以看看:

  • UTF-8或UTF-16或UTF-32或UCS-2
  • Unicode支持需要wchar_t吗?

编号UTF-8是一个完全有效的使用char*string的Unicode编码。 它的优点是,如果你的程序对非ASCII字节是透明的(例如,一个行结束转换器,它对\r\n起作用,但是不改变其它字符),你将不需要做任何改变!

如果你使用UTF-8,你需要改变所有的假设: char = character(例如,不要在循环中调用toupper )或者char = screen列(例如文本换行)。

如果你使用UTF-32,你将拥有固定宽度字符的简单性(但不是固定宽度的字形 ,但需要改变所有string的types)。

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

我build议积极地避免 wchar_t因为它不是跨平台的:有时它是UTF-32,有时是UTF-16,有时它是一个预编码的东亚编码。 我build议使用typedefs

更重要的是, 避免TCHAR

据我所知,wchar_t是依赖于实现的(从这篇wiki文章中可以看出)。 这不是unicode。

我不会相信任何标准的库实现。 只要推出自己的unicodetypes。

 #include <windows.h> typedef unsigned char utf8_t; typedef unsigned short utf16_t; typedef unsigned long utf32_t; int main ( int argc, char *argv[] ) { int msgBoxId; utf16_t lpText[] = { 0x03B1, 0x0009, 0x03B2, 0x0009, 0x03B3, 0x0009, 0x03B4, 0x0000 }; utf16_t lpCaption[] = L"Greek Characters"; unsigned int uType = MB_OK; msgBoxId = MessageBoxW( NULL, lpText, lpCaption, uType ); return 0; }