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、编码、标准和可移植性 的相关文章

  • 我想优化这个短循环

    我想优化这个简单的循环 unsigned int i while j 0 j is an unsigned int with a start value of about N 36 000 000 float sub 0 i 1 unsig
  • WritePrivateProfileString 未在末尾添加属性

    我正在使用以下命令在 ini 文件中写入一些属性WritePrivateProfileString函数并且一切正常 但是当我添加多行文本时 出现了问题 这是代码和输出 WritePrivateProfileString T General
  • 此上下文中仅支持实体类型、枚举类型或基本类型

    我目前正在开发一个搜索页面 我只需要返回主题的主题详细信息列表 其中包含存储在 int ST 中的所有主题标签 id 目前 ST null true ST Contains b ThemeTagID 行似乎给了我一个错误 附加信息 无法创建
  • IssuerSigningKeyResolver 调用异步方法

    我们使用 IssuerSigningKeyResolver 它是 Microsoft IdentityModel Tokens 的一部分 用于令牌验证并接受非异步委托 我们调用一个异步方法 这将导致阻塞调用 因此想知道使用它的正确方法是什么
  • 我是否必须使用我的数据库训练 Viola-Jones 算法才能获得准确的结果?

    我尝试提取面部数据库的面部特征 但我认识到 Viola Jones 算法在两种情况下效果不佳 当我尝试单独检测眼睛时 当我尝试检测嘴巴时 运作不佳 检测图像的不同部分 例如眼睛或嘴巴 或者有时会检测到其中几个 这是不可能的情况 我使用的图像
  • 如何在 ASP.NET 5/vNext/Core 中使用 Elmah?

    我对如何在 ASP NET 5 MVC 6 项目中使用 Elmah 有点困惑 我从 nuget 得到了包 它添加了 Elmah Mvc 2 1 2 到project json 中的依赖项 我不知道从这里到哪里去 以前 nuget 会向 we
  • 使用不存在和联接的 SQL 查询到 LINQ 语法

    我的 SQL 查询如下所示 在 SQL 中运行良好 我需要将其转换为 LINQ 语法 SQL SELECT Key Id FROM LocalizationKeys AS lk WHERE NOT EXISTS SELECT 1 FROM
  • 如何使用 PowerShell 使用 C# DLL 中存在的类的 New-Object

    例如 我有一个 C 类 public class MyComputer PSObject public string UserName get return userName set userName value private strin
  • 慢速 WPF 文本框

    我正在开发一个简单的串行数据查看器 它将用于观察传输到计算机串行端口之一的数据 我使用 C 和 WPF 编写了一个测试应用程序 它只是将最近读取的行放入文本块中 但是 它会跳过所有其他行 我的理论是 在 WPF 渲染窗口之前 新数据会被放入
  • 树结构的序列化/反序列化

    我试图找出保存 序列化 并稍后打开 反序列化 树结构的最佳方法 我的结构由具有不同属性的各种对象类型组成 但每个对象类型都继承自基本抽象 Node 类 每个节点都有唯一的 ID GUID 并且有一个 AddSuperNode Node nd
  • WinForms TreeView - 如何手动“突出显示”节点(就像被单击一样)

    我需要知道如何让以编程方式选择的节点以图形方式处于 选定 状态 就像用户单击它一样 SelectedNode 仅使这一节点在内部被选中 非常感谢 它没有显示为突出显示的原因是由于树视图没有焦点 这是我的测试表单上的按钮单击事件 TreeVi
  • 如何检测机器是否加入域?

    如何检测计算机是否已加入 Active Directory 域 相对于工作组模式 如果没有必要的话 不要用 pinvoke 来愚弄 参考System DirectoryServices 然后调用 System DirectoryServic
  • 弹出窗口或弹出窗口显示附加信息

    我想在我的应用程序顶部显示带有附加信息的弹出窗口 我的信息是Listview大约 500 个项目我都尝试过 有问题flyout gt 它里面可能有scrollViewer 所以我的列表视图不能正确虚拟化 其他一切都可以 有我的代码 Flyo
  • 如何存储将被多个不同类访问的字符串常量? [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 关于堆栈溢出有太多不同的答案 声明一个命名空间 并在 hpp 文件中将所有字符串标记为 extern const 并在 cpp 文件中放置它们的
  • 最佳实践:从属性中抛出异常

    什么时候适合从属性 getter 或 setter 中抛出异常 什么时候不合适呢 为什么 关于这个主题的外部文档的链接会很有帮助 谷歌搜索结果出奇的少 Microsoft 在以下位置提供了有关如何设计属性的建议 http msdn micr
  • C memcpy 二维数组

    我正在尝试使用将一个二维数组复制到另一个memcpy 我的代码 include
  • MSAL.Net 没有帐户或登录提示传递到 AcquireTokenSilent 调用

    我见过很多相同或类似的问题 并尝试了他们所有的答案 如果有的话 但这些都不适合我 我在用着这个例子 https github com Azure Samples ms identity javascript angular spa aspn
  • 在 asp.net MVC 控制器中调用异步外部 Web 服务

    在 Asp net MVC 控制器 GET 方法 中 我调用外部 Web 服务 用于 IP 地理定位 返回 IP 位置的 json 数据 如何使调用异步 以便堆栈可以在等待服务响应时继续 当 GEO IP 请求完成后 我希望能够更新数据库
  • scanf() 不等待用户输入[重复]

    这个问题在这里已经有答案了 我正在使用 c 中的双向链表来制作树 我在该函数中使用递归调用 但不知何故它不起作用 我的代码是 struct node int data struct node right struct node left s
  • 计算 .NET Core 项目的代码指标?

    我正在研究 ASP NET Core 和 NET Core 项目 对于经典的 C 项目 Visual Studio 2015 具有计算代码指标的功能 对于 NET Core 预览版 2 工具中缺少支持 在工具更加完整之前 有人知道解决方法吗

随机推荐