C编程:如何编程为Unicode?
严格的Unicode编程需要什么先决条件?
这是否意味着我的代码不应该在任何地方使用char
types,并且需要使用可以处理wint_t
和wchar_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_t
types相同(在7.20.1.2中描述); 和char32_t
这是一个用于32位字符的无符号整数types,与
uint_least32_t
types相同(在7.20.1.2中也有描述)。
(翻译交叉引用: <stddef.h>
定义了size_t
, <wchar.h>
定义了mbstate_t
, <stdint.h>
定义了uint_least16_t
和uint_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_t
string的Windows API需要UTF-16编码。 还要注意,这与UCS-2不同。 注意代理对。 这个testing页面有启发性testing。 -
如果你在Windows上编程,你不能使用
fopen()
,fread()
,fwrite()
等等,因为它们只带有char *
而不理解UTF-8编码。 使可移植性痛苦。
-D B
要严格的Unicode编程:
- 只能使用支持Unicode的stringAPI( 不是
strlen
,strcpy
,…而是它们的wstrlen
wsstrcpy
对应wstrlen
,wsstrcpy
,…) - 处理文本块时,请使用允许存储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 str
与bytes
或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; }