【问题标题】:How is const std::wstring encoded and how to change to UTF-16const std::wstring 如何编码以及如何更改为 UTF-16
【发布时间】:2020-11-30 20:25:47
【问题描述】:

我创建了这个最小的工作 C++ 示例 sn-p 来比较 std::stringstd::wstring 中的字节(通过它们的十六进制表示)在定义带有德语非 ASCII 字符的字符串时。

#include <iostream>
#include <iomanip>
#include <string>

int main(int, char**) {
    std::wstring wstr = L"äöüß";
    std::string str = "äöüß";

    for ( unsigned char c : str ) {
        std::cout << std::setw(2) << std::setfill('0') << std::hex << static_cast<unsigned short>(c) << ' ';
    }
    std::cout << std::endl;

    for ( wchar_t c : wstr ) {
        std::cout << std::setw(4) << std::setfill('0') << std::hex << static_cast<unsigned short>(c) << ' ';
    }
    std::cout << std::endl;

    return 0;
}

这个sn-p的输出是

c3 a4 c3 b6 c3 bc c3 9f 
00c3 00a4 00c3 00b6 00c3 00bc 00c3 0178

我在运行自身 Windows 10 64 位 Pro 的 PC 上运行此程序,使用版本 16.8.1 中的 MSVC 2019 社区版 编译,使用构建系统 cmake 跟随CMakeLists.txt

cmake_minimum_required(VERSION 3.0.0)
project(wstring VERSION 0.1.0)

set(CMAKE_CXX_STANDARD 17)

include(CTest)
enable_testing()

add_executable(wstring main.cpp)

set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)

我读到,std::strings 是基于 char 类型的,这是一个单字节。我看到我的 sn-p 的输出表明 strstd::string 变量)是 UTF-8 编码的。我读到,Microsoft 编译器使用 wchar_ts 和 2 个字节组成 std::wstrings(而不是 4 字节 wchar_ts,例如 GNU gcc),因此期望 wstrstd::wstring 变量)被(任何类型的)UTF-16 编码。但我无法弄清楚为什么“ß”(拉丁升音 s)被编码为 0x00c30178 我原本期望 0x00df 代替。有人请告诉我:

  • 为什么会这样?
  • 我怎样才能得到 UTF-16 编码的std::wstrings(Big Endian 没问题,我不介意 BOM)?我可能需要以某种方式告诉编译器吗?
  • 这是什么编码?

编辑 1

更改了标题,因为它不适合问题(实际上 UTF-8 和 UTF-16 是不同的编码,所以我自己的新答案已经...)

编辑 2

忘了说:我使用上述编译器的amd64目标

编辑 3

如果按照 dxiv 在 cmets 中指出的那样添加 /utf-8 标志(请参阅 his linked SO-Post),我会得到所需的输出

c3 a4 c3 b6 c3 bc c3 9f
00e4 00f6 00fc 00df

对我来说看起来像 UTF-16-BE(无 BOM)。由于我对 cmake 命令的正确顺序有疑问,这是我当前的 CmakeLists.txt 文件。重要的是将add_compile_options 命令放在add_executable 命令之前(为方便起见,我添加了注意事项)

cmake_minimum_required(VERSION 3.0.0)
project(enctest VERSION 0.1.0)

set(CMAKE_CXX_STANDARD 17)

include(CTest)
enable_testing()

if (MSVC)
  message(NOTICE "compiling with MSVC")
  add_compile_options(/utf-8)
endif()

add_executable(enctest main.cpp)

set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)

我发现 if-endif 方式比生成器语法方式更具可读性,但写成 add_compile_options("$&lt;$&lt;CXX_COMPILER_ID:MSVC&gt;:/utf-8&gt;") 也可以。

注意:对于 Qt-Projects,.pro 文件有一个不错的开关(参见 this Qt-Form post

win32 {
    QMAKE_CXXFLAGS += /utf-8
}

我的问题的第一部分仍然是开放的:0x00c30178 用于“ß”(拉丁升音 s)的编码是什么?

【问题讨论】:

  • 你如何editor保存文件?你看过吗?在您自己的源文件中使用十六进制编辑器查看?
  • @Someprogrammerdude 刚刚这样做了,Notepad++ 告诉我 main.cppUTF-8 编码的,HxD 向我显示了两个字符串的 C3 A4 C3 B6 C3 BC C3 9F。我使用带有 CMake 工具扩展的 Visual Studio Code 来创建项目并对其进行编辑。但我使用 Qt Creator 得到了相同的结果。
  • @Martin 是带还是不带 BOM 的 UTF-8,你使用 /source-charset:utf-8
  • @dxiv 据我所知,UTF-8 不包含 BOM,因为仅当字符类型由超过 1 个字节组成时才需要告知字节序。无论如何,该文件不是以 BOM 开头,而是以 0x23 开头,即“#”。关于source-charset 不行,如果cmake 没有自动设置的话。我使用CMakeLists.txt。如何使用 cmake 进行设置?
  • @Martin VS 使用 BOM 来识别源文件的编码。如果没有 BOM,它“假定源文件是使用当前用户代码页编码的”,这不是您想要的。另见Possible to force CMake/MSVC to use UTF-8 encoding for source files without a BOM? C4819

标签: visual-c++ utf-8 c++17 utf-16 wstring


【解决方案1】:

如 cmets 中所述,源 .cpp 文件是 UTF-8 编码的。如果没有 BOM,也没有显式的 /source-charset:utf-8 开关,Visual C++ 编译器默认假定源文件保存在活动代码页编码中。来自Set Source Character Set 文档:

默认情况下,Visual Studio 会检测字节顺序标记以确定源文件是否采用编码的 Unicode 格式,例如 UTF-16 或 UTF-8。如果未找到字节顺序标记,则假定源文件使用当前用户代码页进行编码,除非您使用 /source-charset 选项指定字符集名称或代码页。

äöüß 的 UTF-8 编码是C3 A4 C3 B6 C3 BC C3 9F,因此行:

    std::wstring wstr = L"äöüß";

被编译器视为:

    std::wstring wstr = L"\xC3\xA4\xC3\xB6\xC3\xBC\xC3\x9F"`;

假设活动代码页是通常的Windows-1252,(扩展的)字符映射为:

    win-1252    char    unicode

      \xC3       Ã       U+00C3
      \xA4       ¤       U+00A4
      \xB6       ¶       U+00B6
      \xBC       ¼       U+00BC
      \x9F       Ÿ       U+0178

因此L"\xC3\xA4\xC3\xB6\xC3\xBC\xC3\x9F" 被翻译成:

    std::wstring wstr = L"\u00C3\u00A4\u00C3\u00B6\u00C3\u00BC\u00C3\u0178"`;

为避免此类(错误)翻译,需要通过显式传递/source-charset:utf-8(或/utf-8)编译器开关来告知Visual C++ 源文件编码为UTF-8。对于基于 CMake 的项目,这可以使用 add_compile_options 完成,如 Possible to force CMake/MSVC to use UTF-8 encoding for source files without a BOM? C4819 所示。

【讨论】:

    【解决方案2】:

    因此期望 wstr(std::wstring 变量)是(任何类型的)UTF-16 编码

    std::wstring 未指定编码。它是“宽字符”序列,用于某种宽字符(由实现定义)。

    standard library 中定义了用于与不同编码进行转换的转换方面。

    【讨论】:

    • 我也读过,但是编译器仍然应该创建至少一些有效的编码,然后我可以将我的 wstring 转换为 UTF-16?否则,在我的程序中定义硬编码文本的兼容性低于任何有效编码的外部文件。
    • 这就是“定义的实现”的意思——但我想说的是不同的编译器可能会做不同的事情。
    猜你喜欢
    • 1970-01-01
    • 2011-11-01
    • 2013-09-26
    • 1970-01-01
    • 2021-07-05
    • 2012-06-20
    • 2011-05-13
    • 2012-03-13
    • 1970-01-01
    相关资源
    最近更新 更多