WChars、编码、标准和可移植性

2024-03-06

以下问题可能不属于 SO 问题;如果超出范围,请随时告诉我离开。这里的问题基本上是:“我是否正确理解了 C 标准,这是处理问题的正确方法吗?”

我想请求对我对 C(以及 C++ 和 C++0x)中字符处理的理解进行澄清、确认和更正。首先,一个重要的观察:

可移植性和序列化是正交的概念。

便携式的东西是像C这样的东西,unsigned int, wchar_t。可序列化的东西是这样的uint32_t或 UTF-8。 “可移植”意味着您可以重新编译相同的源代码并在每个受支持的平台上获得工作结果,但二进制表示可能完全不同(或者甚至不存在,例如 TCP-over-Carrier Pig)。另一方面,可序列化的东西总是有same代表,例如我可以在 Windows 桌面、手机或牙刷上阅读该 PNG 文件。可移植的东西是内部的,可序列化的东西处理 I/O。可移植的东西是类型安全的,可序列化的东西需要类型双关。 前言>

当谈到 C 中的字符处理时,有两组事情分别与可移植性和序列化相关:

  • wchar_t, setlocale(), mbsrtowcs()/wcsrtombs(): C 标准没有提到“编码”;事实上,它与任何文本或编码属性完全无关。它只说“你的入口点是main(int, char**);你得到一个类型wchar_t它可以容纳您系统的所有字符;您可以获得读取输入字符序列并将其转换为可用的 wstring 的函数,反之亦然。

  • iconv()UTF-8,16,32:在明确定义的、明确的、固定的编码之间进行转码的函数/库。 iconv 处理的所有编码都得到普遍理解和认可,但有一个例外。

可移植的、与编码无关的 C 世界与其wchar_t可移植的字符类型和确定性的外部世界是WCHAR-T 和 UTF 之间的 iconv 转换.

那么,我是否应该始终将字符串存储在与编码无关的 wstring 中,通过 CRT 进行接口wcsrtombs(),并使用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 这样的专用库。)

Updates

继许多非常好的评论之后,我想添加一些观察结果:

  • 如果您的应用程序明确想要处理 Unicode 文本,您应该将iconv-转换部分核心及使用uint32_t/char32_t-内部使用 UCS-4 字符串。

  • Windows:虽然使用宽字符串通常没问题,但与控制台(就此而言,任何控制台)的交互似乎受到限制,因为似乎不支持任何合理的多字节控制台编码,并且mbstowcs本质上是无用的(除了微不足道的扩大)。从 Explorer-drop 接收宽字符串参数GetCommandLineW+CommandLineToArgvW可以工作(也许应该有一个单独的 Windows 包装器)。

  • 文件系统:文件系统似乎没有任何编码的概念,只是将任何以空结尾的字符串作为文件名。大多数系统采用字节字符串,但 Windows/NTFS 采用 16 位字符串。在发现哪些文件存在以及处理该数据时(例如char16_t不构成有效 UTF16 的序列(例如裸代理)是有效的 NTFS 文件名)。标准Cfopen无法打开所有 NTFS 文件,因为没有可能的转换可以映射到所有可能的 16 位字符串。使用 Windows 特定的_wfopen可能需要。作为推论,通常没有明确定义的概念来表示给定的文件名包含“多少个字符”,因为首先没有“字符”的概念。买者自负。


这是仅使用纯标准 C/C++ 编写惯用的、可移植的、通用的、与编码无关的程序核心的正确方法吗

不,并且根本没有办法满足所有这些属性,至少如果您希望您的程序在 Windows 上运行的话。在 Windows 上,您必须忽略几乎所有地方的 C 和 C++ 标准,并专门使用wchar_t(不一定是内部的,而是系统的所有接口)。例如,如果您从

int main(int argc, char** argv)

您已经失去了对命令行参数的 Unicode 支持。你必须写

int wmain(int argc, wchar_t** argv)

相反,或使用GetCommandLineW函数,C 标准中没有指定这些函数。

进一步来说,

  • Windows 上任何支持 Unicode 的程序都必须主动忽略 C 和 C++ 标准,例如命令行参数、文件和控制台 I/O,或者文件和目录操作。这当然不是惯用语。请改用 Microsoft 扩展或包装器,例如 Boost.Filesystem 或 Qt。
  • 可移植性实现起来极其困难,尤其是对于 Unicode 支持。你真的必须做好准备,你认为你所知道的一切都可能是错误的。例如,您必须考虑用于打开文件的文件名可能与实际使用的文件名不同,并且两个看似不同的文件名可能代表同一个文件。创建两个文件后a and b,您最终可能会得到一个文件c,或两个文件d and e,其文件名与您传递给操作系统的文件名不同。您要么需要一个外部包装库,要么需要大量#ifdefs.
  • 编码不可知性通常在实践中不起作用,特别是如果您想要便携的话。你必须知道wchar_t是 Windows 上的 UTF-16 代码单元char通常(bot 并不总是)Linux 上的 UTF-8 代码单元。编码意识通常是更理想的目标:确保您始终知道您使用哪种编码,或者使用将它们抽象出来的包装器库。

我想我必须得出这样的结论:除非您愿意使用额外的库和特定于系统的扩展,并在其中投入大量精力,否则完全不可能用 C 或 C++ 构建可移植的支持 Unicode 的应用程序。不幸的是,大多数应用程序已经无法完成相对简单的任务,例如“将希腊字符写入控制台”或“以正确的方式支持系统允许的任何文件名”,而此类任务只是实现真正的 Unicode 支持的第一步。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

WChars、编码、标准和可移植性 的相关文章

随机推荐