Jak jest kodowane w const std :: wstring i jak zmienić na UTF-16

Nov 30 2020

Utworzyłem ten minimalny działający przykładowy fragment kodu C ++, aby porównać bajty (według ich reprezentacji szesnastkowej) w a std::stringi a std::wstringpodczas definiowania ciągu z niemieckimi znakami spoza ASCII w obu typach.

#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;
}

Dane wyjściowe tego fragmentu to

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

Uruchomiłem to na komputerze PC z systemem Windows 10 64-bit Pro , kompilując z MSVC 2019 Community Edition w wersji 16.8.1, używając polecenia cmake systemu kompilacji z następującymiCMakeLists.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)

Czytałem, że std::stringsą one oparte na chartypie, który jest jednobajtowy. Widzę, że dane wyjściowe mojego fragmentu kodu wskazują, że str( std::stringzmienna) jest zakodowana w UTF-8 . Czytałem, że kompilatory Microsoftu używają wchar_ts z 2 bajtami do tworzenia std::wstrings (zamiast 4 bajtów wchar_tprzez np. GNU gcc) i dlatego oczekiwałbym, że wstr( std::wstringzmienna) będzie (dowolnego rodzaju) zakodowana w UTF-16 . Ale nie mogę zrozumieć, dlaczego „ß” (łacińskie ostre s) jest kodowane tak 0x00c30178, jak się spodziewałem 0x00df. Niech ktoś mi powie:

  • Dlaczego tak się dzieje?
  • Jak mogę skończyć z kodowaniem UTF-16 std::wstring(Big Endian byłby w porządku, nie mam nic przeciwko BOM)? Czy prawdopodobnie muszę jakoś powiedzieć kompilatorowi?
  • Co to za kodowanie?

EDYCJA 1

zmieniony tytuł, ponieważ nie pasował poprawnie do pytań (a właściwie UTF-8 i UTF-16 to różne kodowania, więc ja sam sobie nową odpowiedź już ...)

EDYCJA 2

zapomniałem wspomnieć: używam amd64celu wspomnianego kompilatora

EDYCJA 3

jeśli dodajesz /utf-8flagę, jak wskazał w komentarzach dxiv (patrz jego połączony SO-Post ), otrzymuję żądane wyjście

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

który dla mnie wygląda jak UTF-16-BE (bez BOM). Ponieważ miałem problemy z poprawną kolejnością poleceń cmake, jest to mój bieżący CmakeLists.txtplik. Ważne jest, aby umieścić add_compile_optionspolecenie przed add_executablepoleceniem (dla wygody dodałem Powiadomienie)

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)

Uważam, że if-endifsposób jest bardziej czytelny niż generator-składnia, ale zamiast tego pisanie też by działało.add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")

Uwaga: W przypadku projektów Qt istnieje fajny przełącznik dla .propliku (zobacz ten post Qt-Form )

win32 {
    QMAKE_CXXFLAGS += /utf-8
}

Wciąż pierwsza część mojego pytania jest otwarta: jakie jest kodowanie 0x00c30178dla „ß” (łacińskie s)?

Odpowiedzi

5 dxiv Dec 01 2020 at 16:41

Jak wyjaśniono w komentarzach, .cppplik źródłowy jest zakodowany w UTF-8. Bez BOM i bez jawnego /source-charset:utf-8przełącznika kompilator Visual C ++ domyślnie przyjmuje, że plik źródłowy jest zapisywany w aktywnym kodowaniu strony kodowej. Z dokumentacji Set Source Character Set :

Domyślnie program Visual Studio wykrywa znacznik kolejności bajtów, aby określić, czy plik źródłowy jest w zakodowanym formacie Unicode, na przykład UTF-16 lub UTF-8. Jeśli nie zostanie znaleziony żaden znacznik kolejności bajtów, zakłada się, że plik źródłowy jest kodowany przy użyciu bieżącej strony kodowej użytkownika, chyba że określisz nazwę zestawu znaków lub stronę kodową za pomocą opcji / source-charset.

Kodowanie UTF-8 äöüßto C3 A4 C3 B6 C3 BC C3 9F, a więc wiersz:

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

jest postrzegany przez kompilator jako:

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

Zakładając, że aktywną stroną kodową jest zwykły Windows-1252 , (rozszerzone) znaki są mapowane jako:

    win-1252    char    unicode

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

Dlatego L"\xC3\xA4\xC3\xB6\xC3\xBC\xC3\x9F"jest tłumaczony na:

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

Aby uniknąć takiego (błędnego) tłumaczenia, Visual C ++ musi otrzymać informację, że plik źródłowy jest zakodowany jako UTF-8 przez przekazanie jawnego /source-charset:utf-8(lub /utf-8) przełącznika kompilatora. W przypadku projektów opartych na CMake można to zrobić za pomocą, add_compile_optionsjak pokazano w sekcji Możliwe, aby wymusić na CMake / MSVC użycie kodowania UTF-8 dla plików źródłowych bez BOM? C4819 .

MarshallClow Nov 30 2020 at 20:50

w związku z tym oczekuje, że wstr (zmienna std :: wstring) będzie (dowolnego rodzaju) zakodowana w UTF-16

std::wstringnie określa kodowania. Jest to sekwencja „szerokich znaków” dla pewnego rodzaju szerokich znaków (które są zdefiniowane w implementacji).

W bibliotece standardowej zdefiniowano aspekty konwersji do konwertowania do / z różnych kodowań.