【发布时间】:2011-09-12 03:35:08
【问题描述】:
以下内容可能不属于 SO 问题;如果超出范围,请随时告诉我离开。这里的问题基本上是,“我是否正确理解了 C 标准,这是处理事情的正确方式吗?”
我想就我对 C(以及 C++ 和 C++0x)中字符处理的理解提出澄清、确认和更正。首先,一个重要的观察:
可移植性和序列化是正交的概念。
可移植的东西是 C、unsigned int、wchar_t。可序列化的东西是 uint32_t 或 UTF-8 之类的东西。 “可移植”意味着您可以重新编译相同的源代码并在每个支持的平台上获得工作结果,但二进制表示可能完全不同(甚至不存在,例如 TCP-over-carrier pigeon)。另一方面,可序列化的东西总是有 same 表示,例如我可以在 Windows 桌面、手机或牙刷上阅读的 PNG 文件。可移植的东西是内部的,可序列化的东西处理 I/O。可移植的东西是类型安全的,可序列化的东西需要类型双关。 序言>
说到C中的字符处理,有两组分别与可移植性和序列化相关:
-
wchar_t、setlocale()、mbsrtowcs()/wcsrtombs():C 标准没有提到“编码”;事实上,它与任何文本或编码属性完全无关。它只说“你的入口点是main(int, char**);你会得到一个类型wchar_t,它可以保存你系统的所有字符;你可以获得读取输入字符序列并将它们变成可用的wstrings的函数,反之亦然。 iconv()和 UTF-8,16,32:用于在定义明确的、明确的、固定的编码之间进行转码的函数/库。 iconv 处理的所有编码都得到普遍理解和认可,只有一个例外。
可移植的、与编码无关的 C 及其 wchar_t 可移植字符类型与确定性外部世界之间的桥梁是 WCHAR-T 和 UTF 之间的 iconv 转换。
那么,我是否应该始终在内部将字符串存储在与编码无关的 wstring 中,通过 wcsrtombs() 与 CRT 接口,并使用 iconv() 进行序列化?从概念上讲:
my program
<-- wcstombs --- /==============\ --- iconv(UTF8, WCHAR_T) -->
CRT | wchar_t[] | <Disk>
--- mbstowcs --> \==============/ <-- iconv(WCHAR_T, UTF8) ---
|
+-- iconv(WCHAR_T, UCS-4) --+
|
... <--- (adv. Unicode malarkey) ----- libicu ---+
实际上,这意味着我会为我的程序入口点编写两个样板包装器,例如对于 C++:
// Portable wmain()-wrapper
#include <clocale>
#include <cwchar>
#include <string>
#include <vector>
std::vector<std::wstring> parse(int argc, char * argv[]); // use mbsrtowcs etc
int wmain(const std::vector<std::wstring> args); // user starts here
#if defined(_WIN32) || defined(WIN32)
#include <windows.h>
extern "C" int main()
{
setlocale(LC_CTYPE, "");
int argc;
wchar_t * const * const argv = CommandLineToArgvW(GetCommandLineW(), &argc);
return wmain(std::vector<std::wstring>(argv, argv + argc));
}
#else
extern "C" int main(int argc, char * argv[])
{
setlocale(LC_CTYPE, "");
return wmain(parse(argc, argv));
}
#endif
// Serialization utilities
#include <iconv.h>
typedef std::basic_string<uint16_t> U16String;
typedef std::basic_string<uint32_t> U32String;
U16String toUTF16(std::wstring s);
U32String toUTF32(std::wstring s);
/* ... */
这是仅使用纯标准 C/C++ 编写惯用的、可移植的、通用的、与编码无关的程序核心的正确方法,以及使用 iconv 的明确定义的 UTF I/O 接口吗? (请注意,Unicode 规范化或变音符号替换等问题超出了范围;只有在您确定您确实需要 Unicode(与您可能喜欢的任何其他编码系统相反)之后,才是处理这些问题的时候了细节,例如使用像 libicu 这样的专用库。)
更新
在许多非常好的 cmets 之后,我想补充几点意见:
如果您的应用程序明确想要处理 Unicode 文本,您应该将
iconv-conversion 设为核心的一部分,并在 UCS-4 内部使用uint32_t/char32_t-strings。Windows:虽然使用宽字符串通常没问题,但与控制台(就此而言,任何控制台)的交互似乎是有限的,因为似乎不支持任何合理的多字节控制台编码和
mbstowcs基本上是无用的(除了微不足道的扩大)。从例如 Explorer-drop 接收宽字符串参数以及GetCommandLineW+CommandLineToArgvW可以工作(也许应该有一个单独的 Windows 包装器)。文件系统:文件系统似乎没有任何编码概念,只是将任何以空字符结尾的字符串作为文件名。大多数系统采用字节字符串,但 Windows/NTFS 采用 16 位字符串。在发现哪些文件存在以及处理该数据时必须小心(例如,不构成有效 UTF16 的
char16_t序列(例如裸代理)是有效的 NTFS 文件名)。标准 Cfopen无法打开所有 NTFS 文件,因为没有可能的转换将映射到所有可能的 16 位字符串。可能需要使用特定于 Windows 的_wfopen。作为推论,通常没有明确定义的“多少个字符”概念构成一个给定的文件名,因为首先没有“字符”的概念。警告购买者。
【问题讨论】:
-
虽然我不认为
wmain应该是extern "C"如果它需要一个std::vector。 (我认为您不应该将 C++ 类传递给具有 C 链接的函数。) -
“你得到了一个 wchar_t 类型,它可以保存你系统的所有字符”——不,比这更糟。在 Windows 中,wchar_t 可能只包含代理对的一半。对于这些字符,您需要两个 wchar_t 对象来包含整个字符。还可能会更糟糕的。如果我没记错的话,一个令人讨厌但合法的实现可能会使 wchar_t 与 unsigned char 相同。
-
是的,代理不是一个字符,这就是为什么你没有得到一个可以容纳所有系统字符的 wchar_t 类型的原因。
-
如果定义了
__STDC_ISO_10646__,则wchar_t值是Unicode 代码点。 C1x 有__STDC_UTF_16__和__STDC_UTF_32__分别对应char16_t和char32_t,C++0x 似乎没有这最后两个宏。 -
只有一句话要说:阅读utf8everywhere.org 了解如何、为什么、有多冷、为什么会发生、现在该做什么以及其他人应该做什么。