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

Я создал этот минимальный рабочий фрагмент примера C++ для сравнения байтов (по их шестнадцатеричному представлению) в std::string и 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. strong> с последующим 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_ts, например, GNU gcc) и поэтому ожидают, что wstr (переменная std::wstring) будет (любым) UTF-16 закодировано. Но я не могу понять, почему ß (латинское диез s) кодируется как 0x00c30178, вместо этого я ожидал 0x00df. Может кто-нибудь, пожалуйста, скажите мне:

  • Почему это происходит?
  • Как я могу получить std::wstrings в кодировке UTF-16 (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 для ß (латинское диез s)?


person Martin    schedule 30.11.2020    source источник
comment
Как вы редактор сохраняете файл? Вы смотрели, например, в hex-редакторе собственный исходный файл посмотреть?   -  person Some programmer dude    schedule 30.11.2020
comment
@Someprogrammerdude только что сделал это, Notepad++ сообщает мне, что main.cpp закодирован в UTF-8, HxD показывает мне C3 A4 C3 B6 C3 BC C3 9F для обеих строк. Я использую код Visual Studio с расширением CMake Tools для создания проекта и его редактирования. Но я получаю тот же результат, используя Qt Creator.   -  person Martin    schedule 30.11.2020
comment
@Martin Это UTF-8 со спецификацией или без нее, и используете ли вы /source-charset:utf-8?   -  person dxiv    schedule 30.11.2020
comment
@dxiv, насколько мне известно, UTF-8 не содержит спецификаций, поскольку они необходимы только для информирования о порядке следования байтов, если типы символов состоят из более чем 1 байта. В любом случае файл начинается не со спецификации, а с 0x23, что означает #. По поводу source-charset нет, если cmake не устанавливает его автоматически. Я использую CMakeLists.txt. Как я могу установить это с помощью cmake?   -  person Martin    schedule 01.12.2020
comment
@Martin VS использует спецификацию для определения кодировки исходного файла. Без спецификации он предполагает, что исходный файл закодирован с использованием текущей пользовательской кодовой страницы, а это не то, что вам нужно. См. также «> Можно ли заставить CMake/MSVC использовать кодировку UTF-8 для исходных файлов без спецификации? C4819.   -  person dxiv    schedule 01.12.2020
comment
@dxiv спасибо, что указал мне на эту ветку SO. Ответ там кажется половиной ответа на мой пост, который на самом деле более важная половина. Мне все еще было бы интересно узнать, как появляется 0x00c30178, но если вы опубликуете свой комментарий в качестве ответа, я отмечу его как правильный.   -  person Martin    schedule 01.12.2020
comment
@Martin Опубликовано как ответ, включая пошаговые инструкции ß --› 0xC3 0x9F --› U+00C3 U+0178.   -  person dxiv    schedule 01.12.2020
comment
@Martin насколько я знаю, UTF-8 не содержит спецификаций - спецификация не рекомендуется в UTF-8, но, тем не менее, она определена. они необходимы только для информирования о порядке следования байтов, если типы символов состоят из более чем 1 байта — спецификация действительно определяет порядок байтов UTF-16/UTF -32, но ТАКЖЕ различает разные UTF. Спецификация UTF-32 отличается от спецификации UTF-16, отличается от спецификации UTF-8, отличается от спецификации UTF-7 и т. д. Спецификация технически представляет собой просто кодированную UTF форму символа Unicode U+FEFF, поэтому вы можете определить используемую UTF, посмотрев на спецификацию   -  person Remy Lebeau    schedule 04.12.2020


Ответы (2)


Как указано в комментариях, исходный файл .cpp имеет кодировку UTF-8. Без спецификации и без явного переключателя /source-charset:utf-8 компилятор Visual C++ по умолчанию предполагает, что исходный файл сохранен в кодировке активной кодовой страницы. Из Установить исходный набор символов:

По умолчанию 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.

person dxiv    schedule 01.12.2020

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

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

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

person Marshall Clow    schedule 30.11.2020
comment
Я тоже это читал, но все же компилятор должен создать хотя бы какую-то действительную кодировку, из которой я могу преобразовать затем мой wstring в UTF-16? В противном случае определение жестко закодированного текста в моей программе менее совместимо, чем внешний файл в любой допустимой кодировке. - person Martin; 30.11.2020
comment
Вот что означает определенная реализация, но я хотел сказать, что разные компиляторы могут делать разные вещи. - person Marshall Clow; 01.12.2020