编码转换和 locale

学习 locale 和编码转换过程中的关键以及问题。

  • 不使用 locale,无法完成编码转换吗?Unicode 自身的 utf-8/16/32 的 存储方式之间的转换 也不行吗?
  • utf-8 对应 charstring;utf-16 对应 wchar_t/wstring,这种理解对吗?所以 boost::locale::conv::to_utf<char>() 就是转 utf-8(而不是 utf-16/32)?boost::locale::conv::to_utf<wchar_t>() 就是转 utf-16 (或 utf-32)?
  • 本地化策略集 locale 和程序运行有关系吗? #139
  • C++17 为什么弃用了 <codecvt>?且 尚无替换方案 吗?
  • 都有哪些本地化策略可选呢?
  • 如果统一使用 Unicode 编码(utf-8/16/32 存储方式无所谓),是否就不需要本地化策略 locale了?它是源于上世纪各个国家、地区闭门造车,独自扩展 ANSI 字符集带来的后果吧?
  • C++11 - Convert to/from UTF-8/wchar_t 里面提到的 UTF8-CPP 和 NoWide 分别是什么?

没有一致的 locale name o(╥﹏╥)o,windows 编程时怎么写

Locale names are not part of the C++ standard… 引用来源

关联问题:

  1. What is the Windows equivalent for en_US.UTF-8 locale?
  2. How to change system locale in Windows 10

第二章

  • wctomb wcstombs ,这两个在处理过字符(串)时更新 std::mbstate_t 类型的全局静态对象,而且不能为二个线程同时调用,这种情况下应使用以下两个函数
  • wcrtomb wcsrtombs,依旧需要在使用之前先调用 setlocale 函数安装指定的系统本地环境或其一部分,作为新的 C 本地环境。
    以下情形会造成 wcstombs/wcsrtombs 转换终止:

    遇到任何非法多字节字符(按照当前 C 本地环境)。

  • setlocale 修改影响本地环境依赖函数的全局状态,故从一个线程调用它,而另一线程同时执行任何下列函数是未定义行为:xxx 参考来源

程序启动过程中,运行任何用户代码前会执行 std::setlocale(LC_ALL, "C"); 的等价代码。

  1. 默认的最小本地环境为 "C",其是否就是最小集?
  2. 如果是最小集,是否意味着如果不设置新的本地环境,转换中文汉字、表情符号等就会造成转换终止?
  3. windows/msvc 是否又做了手脚?

第三章

std::codecvt, 中提到:

Four standalone (locale-independent/本地环境无关) specializations are provided by the standard library:

  • 恒等转换的意义是什么?
  • 在 UTF-16 和 UTF-8 间转换
  • 在 UTF-32 和 UTF-8 间转换
    这两者的存在结合 “locale-independent” 是否间接证明了第一个猜测?
    不存在 UTF-16 和 UTF-32 之间的转换意味着什么?
  • std::codecvt<wchar_t, char, std::mbstate_t> | 在系统原生宽和单字节窄字符集间转换
    系统原生宽字符集:UTF-32(non-Windows)、UCS2(Windows);单字节窄字符集:重点就在于 single-byte,将多字节编码剔除,如此最多也就是 ASCII、ANSI,这部分字符是无需 locale 的,不同的C 本地环境单字节窄字符集都相同。

In addition, every locale object constructed in a C++ program implements its own (locale-specific) versions of these four specializations.

如果上述的理解是正确的,既然都和 locale 无关,那么为什么还要和 locale 混在一起,放在 <locale> 头文件中呢?

另外,针对 codecvt 底部的表格(同 std::wstring_convert 底部表格?)有以下疑惑,这张表格藏着好多内容:

  1. UTF-16 和 UCS2 不同?两者之间还有转换?
  2. UTF-16 和 UTF-32 之间是存在转换的?为什么四个特化不包含这个?

std::codecvt 使用场景是否限于 std::wstring_convert ?

单独的编码转换平面 Codecvt

什么概念?

std::codecvt_utf8 等什么作用?

怎么就这么难理解?难用呢?

国际化?

项目在非中文系统上编译警告(C4566),执行文件有 bug

1
2
3
4
content ="股票/大智慧自定义/热门概念";    // 问题源头
oss << content << xxx;
...
content = boost::locale::conv::to_utf<char>(oss.str(), "GBK"); // 将错误进行下去

写死 GBK(称之为“简体中文版”),这个应该和本地化策略 locale 协同吧?

  1. 软件在非中文系统上无法正常运行吧?编译之后,简中版本拿到其他代码页的系统上应该是能够正常运行的。详细验证见后文。
  2. 项目在 非中文系统 更换系统代码页后无法正常编译、执行吧?done 编译 C4566 警告(意味着已经错了),能够完成编译(编译的可执行文件潜藏 bug);能够(带错)执行,程序不崩溃,但业务逻辑未实现

分析

  • 写死 GBK:不同代码页编译有问题(源于编译时字符串字面值转为执行字符集是依赖本地化策略的),但 936 代码页编译成功后不同代码页(基本上意味着不同语言)执行没有问题(前提是系统存有/能够识别 936 代码页);
  • 要解决编译问题,此处就需要和「编译时字符串字面值转为执行字符集是依赖本地化策略的」本地化策略协同。

怎么分析也觉得“系统的本地化策略集和程序运行无关吧,只关系程序编译才是吧?”

一个程序在运行之前并不知道系统的本地化策略集是什么,程序只有在运行之后才通过locale获得当时的本地化策略集。引用来源

验证2

参考 /execution-charset (Set Execution Character Set) 新增编译选项 /execution-charset:.932

  1. 编译时抛出大量 C4566 警告,但编译能够成功;

    warning C4566: 由通用字符名称“\u5757”表示的字符不能在当前代码页(932)中表示出来。

    事实上出现此警告即意味着错误:源文件中某字符(串)字面值未按预期使用,转换执行字符集时失败了(但编译继续,编译完成),生成的可执行文件中字面值的硬编码是错误的、混乱的。

    此警告由 content ="股票/大智慧自定义/热门概念"; 等简体中文字符抛出

  2. 启动软件,带错运行,通过 boost::locale::conv::to_utf<char>(oss.str(), "GBK");错误的硬编码字节按照 GBK(936 codepage)转成 UTF-8,虽然能够转换完毕,但转换的结果肯定也是错的了

    所以,业务方面请求已发送,但大智慧接口未返回有效数据,进而本地缓存无内容,按键精灵无个股、指数、板块等…

验证1

根据 How to change system locale in Windows 10 描述修改 「非 Unicode 程序中所使用的的当前语言」属性:

当前语言 编译 运行 运行「简体中文」版是否正常
中文(繁体,中国台湾) C4566 警告* 按键精灵缺少板块 正常
英语(美国) 同上 同上 同上

*:警告只限于 initializeBlockInfoWithType() 中的板块路径:

  • 函数所在的 DZHYUNRequest.cpp 文件使用的 UTF-8-BOM 编码(msvc 打开未乱码),「字面值(有 BOM) ,转 utf-8,转 执行字符集(无前缀,未指定相关编译选项,则目标是 current code page/Big5)」,再通过 boost::locale::conv::to_utf<char>(oss.str(), "GBK"); 将 Big5 硬编码字节当做 GBK 转换成 utf-8,所以出错造成按键精灵缺失板块。
  • DZHYUNdef.cpp 文件使用的 GB2312 编码,所以在 msvc 中打开看到原本简体中文的位置变成了乱码(Big5 的皮,GBK 的芯),在后续的「字面值(无 BOM 则依据 current code page/Big5),转 utf-8,转 执行字符集(无前缀,未指定相关编译选项,则目标是 current code page/Big5)」没什么变化,再通过 boost::locale::conv::to_utf<char>(oss.str(), "GBK"); GBK 转换成 utf-8,反而正确了。

心得/结论

首先,源文件使用 UTF-8-BOM 格式保存;其次

  • 只用 u8 字符串 string str = u8"value"
  • 编译选项指定「执行字符集」:/execution-charset:utf-8

上述两种方式等效,可混用!

UTF-8 without BOM

如果排斥 UTF-8 带 BOM,通过「文件-高级保存选项-」可以将编码改为无签名或者安装 ForceUTF8 插件,避免手动调整每一个文件的编码(插件有问题)。需要强调的是,使用无签名 UTF-8 编码,MSVC 会脑残:

warning C4819: 该文件包含不能在当前代码页(936)中表示的字符。

需要明确告知编译器文件编码:/source-charset (Set Source Character Set)),编译选项指定「源字符集」

简写 /utf-8

If you want to set both the source character set and the execution character set to UTF-8, you can use the /utf-8 compiler option as a shortcut. It is equivalent to specifying /source-charset:utf-8 /execution-charset:utf-8 on the command line.

控制台应用程序

windows 的命令行窗口以及 powershell 默认都是无法支持 utf-8 的,调教使其支持 utf-8 挺麻烦的。使用 utf-8 作为执行字符集,在控制台输出时就会产生乱码。所以如果打算通过控制台查看结果,可以使用 utf-8-bom 编码(或者 utf-8 编码并指定 /source-charset:utf-8),但不能修改执行字符集。