编码转换和 locale
学习 locale 和编码转换过程中的关键以及问题。
- 不使用 locale,无法完成编码转换吗?Unicode 自身的 utf-8/16/32 的 存储方式之间的转换 也不行吗?
- utf-8 对应
char
、string
;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… 引用来源
关联问题:
第二章
wctomb
wcstombs
,这两个在处理过字符(串)时更新std::mbstate_t
类型的全局静态对象,而且不能为二个线程同时调用,这种情况下应使用以下两个函数wcrtomb
wcsrtombs
,依旧需要在使用之前先调用setlocale
函数安装指定的系统本地环境或其一部分,作为新的 C 本地环境。
以下情形会造成wcstombs
/wcsrtombs
转换终止:遇到任何非法多字节字符(按照当前 C 本地环境)。
setlocale
修改影响本地环境依赖函数的全局状态,故从一个线程调用它,而另一线程同时执行任何下列函数是未定义行为:xxx 参考来源
程序启动过程中,运行任何用户代码前会执行
std::setlocale(LC_ALL, "C");
的等价代码。
- 默认的最小本地环境为
"C"
,其是否就是最小集? - 如果是最小集,是否意味着如果不设置新的本地环境,转换中文汉字、表情符号等就会造成转换终止?
- 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
底部表格?)有以下疑惑,这张表格藏着好多内容:
- UTF-16 和 UCS2 不同?两者之间还有转换?
- UTF-16 和 UTF-32 之间是存在转换的?为什么四个特化不包含这个?
std::codecvt
使用场景是否限于 std::wstring_convert
?
单独的编码转换平面 Codecvt
什么概念?
std::codecvt_utf8
等什么作用?
怎么就这么难理解?难用呢?
国际化?
项目在非中文系统上编译警告(C4566),执行文件有 bug
1 | content ="股票/大智慧自定义/热门概念"; // 问题源头 |
写死 GBK
(称之为“简体中文版”),这个应该和本地化策略 locale
协同吧?
软件在非中文系统上无法正常运行吧?编译之后,简中版本拿到其他代码页的系统上应该是能够正常运行的。详细验证见后文。- 项目在
非中文系统更换系统代码页后无法正常编译、执行吧?done 编译 C4566 警告(意味着已经错了),能够完成编译(编译的可执行文件潜藏 bug);能够(带错)执行,程序不崩溃,但业务逻辑未实现。
分析
- 写死
GBK
:不同代码页编译有问题(源于编译时字符串字面值转为执行字符集是依赖本地化策略的),但 936 代码页编译成功后不同代码页(基本上意味着不同语言)执行没有问题(前提是系统存有/能够识别 936 代码页); - 要解决编译问题,此处就需要和「编译时字符串字面值转为执行字符集是依赖本地化策略的」本地化策略协同。
怎么分析也觉得“系统的本地化策略集和程序运行无关吧,只关系程序编译才是吧?”
一个程序在运行之前并不知道系统的本地化策略集是什么,程序只有在运行之后才通过locale获得当时的本地化策略集。引用来源
验证2
参考 /execution-charset
(Set Execution Character Set) 新增编译选项 /execution-charset:.932
:
编译时抛出大量 C4566 警告,但编译能够成功;
warning C4566: 由通用字符名称“\u5757”表示的字符不能在当前代码页(932)中表示出来。
事实上出现此警告即意味着错误:源文件中某字符(串)字面值未按预期使用,转换执行字符集时失败了(但编译继续,编译完成),生成的可执行文件中字面值的硬编码是错误的、混乱的。
此警告由
content ="股票/大智慧自定义/热门概念";
等简体中文字符抛出启动软件,带错运行,通过
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
),但不能修改执行字符集。