Как кодируется const std :: wstring и как перейти на UTF-16

Nov 30 2020

Я создал этот минимальный рабочий фрагмент примера C ++ для сравнения байтов (по их шестнадцатеричному представлению) в a std::stringи a std::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;
}

Результатом этого фрагмента будет

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

Я запускал это на ПК под управлением 64-разрядной версии Windows 10 Pro , компилируя с MSVC 2019 Community Edition в версии 16.8.1, используя 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::stringони основаны на charтипе, который является однобайтным. Я вижу, что вывод моего фрагмента указывает, что str( std::stringпеременная) закодирована в UTF-8 . Я читал, что компиляторы Microsoft используют wchar_ts с 2 байтами для создания std::wstrings (вместо 4 байтов wchar_t, например, GNU gcc) и, следовательно, ожидают, что wstr( std::wstringпеременная) будет (любой) закодированной в UTF-16 . Но я не могу понять, почему «ß» (латинское диез) закодировано так, как 0x00c30178я ожидал 0x00df. Кто-нибудь, пожалуйста, скажите мне:

  • Почему это происходит?
  • Как я могу получить кодировку UTF-16 std::wstring(Big Endian было бы хорошо, я не возражаю против спецификации)? Возможно, мне нужно как-то сообщить компилятору?
  • Что это за кодировка?

ИЗМЕНИТЬ 1

изменил заголовок, так как он не соответствовал вопросам должным образом (и на самом деле UTF-8 и UTF-16 - разные кодировки, поэтому я сам уже нашел ответ ...)

ИЗМЕНИТЬ 2

забыл упомянуть: я использую amd64цель упомянутого компилятора

РЕДАКТИРОВАТЬ 3

если добавить /utf-8флаг, как указано в комментариях dxiv (см. его связанный SO-Post ), я получаю желаемый результат

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

который для меня выглядит как UTF-16-BE (без спецификации). Поскольку у меня были проблемы с правильным порядком команд 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("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")

Примечание: для Qt-Projects есть хороший переключатель для .proфайла (см. Этот пост Qt-Form )

win32 {
    QMAKE_CXXFLAGS += /utf-8
}

Тем не менее, первая часть моего вопроса остается открытой: какая кодировка 0x00c30178для "ß" (латинское диез)?

Ответы

5 dxiv Dec 01 2020 at 16:41

Как уточняется в комментариях, исходный .cppфайл имеет кодировку UTF-8. Без спецификации и без явного /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"`;

Чтобы избежать такого (неправильного) перевода, Visual C ++ необходимо сообщить, что исходный файл закодирован как UTF-8, путем передачи явного /source-charset:utf-8(или /utf-8) переключателя компилятора. Для проектов на основе CMake это можно сделать, используя, add_compile_optionsкак показано в разделе Возможно ли заставить CMake / MSVC использовать кодировку UTF-8 для исходных файлов без спецификации? C4819 .

MarshallClow Nov 30 2020 at 20:50

поэтому ожидается, что wstr (переменная std :: wstring) будет (любой) закодированной в UTF-16

std::wstringне указывает кодировку. Это последовательность «широких символов» для некоторых видов широких символов (которые определяются реализацией).

В стандартной библиотеке определены аспекты преобразования для преобразования в / из различных кодировок.