1+1=10

记记笔记,放松一下...

C++与Windows下的UTF-8支持杂谈

随着C++23开始涉足UTF-8源码字符集,以及Windows开始转向UTF-8。C++总算有解决编码问题的苗头了...

问题,由来已久

我喜欢用这个例子,因为代码中的中文的存在,要想代码通用(跨平台),这几行代码的问题可以列一罗筐:

1
2
3
4
5
int main()
{
    char mystr = "老老实实的学问,来不得半点马虎";
    return sizeof mystr;
} 

老外明显没有中日韩群众的感同身受,不过Beman Dawes指出:即使是下面这样简单的代码,C++都无法保证其通用性:

1
int main() {}

由于个人只关注Windows和Linux两个平台,也只涉及MSVC和GCC两种编译器,也基本上被折腾的想吐:

C++23 与 UTF-8

在C++11引入 u8"...."字面量,C++20引入 char8_t 之后

C++23 开始:Support for UTF-8 as a portable source file encoding,详见P2295R6

简单地说,从1998年C++标准化,直到二十五年后,才开始解决源码字符集这一基本问题。

MSVC 与 UTF-8

MSVC对UTF-8支持有两个阶段:

MSVC2010(UTF-8基本可用)

  • 引入了 #pragma execution_character_set("utf-8") 支持执行字符集
  • 支持带BOM的 utf-8 源码字符集

MSVC2015 Update1(UTF-8可用)

  • 引入了 /source-charset:utf-8/execution-charset:utf-8
  • 或者更简单的 /utf-8 选项

但是这也只是解决了窄字符串的执行字符集问题,Windows下API使用UTF-16,而其他平台下使用UTF-8,还是一个大问题

Window API 与 UTF-8

我们知道,Win32 API 通常同时支持 -A-W 两种变体。

  • -A 变体识别系统上配置的 ANSI 代码页,并支持 char*,而
  • -W 变体则在 UTF-16 编码下工作,支持 WCHAR。

长期以来,Windows 一直强调使用 "Unicode" 的 -W 变体,而不是 -A API。(这和其他系统下使用窄字符串UTF8不兼容,编写跨平台代码会很头疼)

然而,最近的版本开始使用 ANSI 代码页和 -A API 来为应用程序引入 UTF-8 支持。如果 ANSI 代码页配置为 UTF-8,那么 -A API 通常会以 UTF-8 操作。这种模式的好处在于,能够支持使用 -A API 构建的现有代码,无需进行代码更改。

看起来,Windows以后可能推荐直接使用-A的API。

注意设置CodePage为 CP_UTF8,对Win32程序来说,注意设置 manifest 文件

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
  <assemblyIdentity type="win32" name="..." version="6.0.0.0"/>
  <application>
    <windowsSettings>
      <activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
    </windowsSettings>
  </application>
</assembly>

关于UTF-8

UTF-8由肯·汤普森(Ken Thompson)和罗布·派克(Rob Pike)于1992年设计。他们的目标是:

  • 兼容性:与ASCII完全兼容(ASCII的字符直接映射为单字节的UTF-8编码)。
  • 灵活性:支持所有Unicode字符。
  • 效率:对常用字符(如英文字母、数字)使用较少的存储空间。
  • 无字节序问题:UTF-8以字节为单位编码,不依赖于字节序(大端或小端)。
字节数 Unicode 范围 二进制格式
1 字节 U+0000 ~ U+007F 0xxxxxxx
2 字节 U+0080 ~ U+07FF 110xxxxx 10xxxxxx
3 字节 U+0800 ~ U+FFFF 1110xxxx 10xxxxxx 10xxxxxx
4 字节 U+10000 ~ U+10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

注意:在UTF-8被首次定义时(RFC 2279),它的设计目标是能够编码到整个31位的空间(即支持码点范围为 U+000000 到 U+7FFFFFFF)。因此,UTF-8最初允许最大6个字节来表示一个字符。

但是 Unicode标准(从版本3.1开始)将码点范围限制在 U+0000 到 U+10FFFF(使用21位空间),这意味着最多需要4个字节就能编码所有Unicode字符。

这个改动与UTF-16的设计限制密切相关(如果哪一天字符不够用了,废掉UTF-16,只保留UTF-32和UTF-8就行了,那时UTF-8可以恢复成6字节)。

参考

  • https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2295r6.pdf
  • https://learn.microsoft.com/en-us/windows/apps/design/globalizing/use-utf8-code-page
  • https://learn.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170
  • https://en.wikipedia.org/wiki/UTF-8

C++ c++, qt