Come viene codificato const std :: wstring e come passare a UTF-16

Nov 30 2020

Ho creato questo frammento di esempio C ++ minimo funzionante per confrontare i byte (dalla loro rappresentazione esadecimale) in a std::stringe a std::wstringquando si definisce una stringa con caratteri tedeschi non ASCII in entrambi i tipi.

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

L'output di questo snippet è

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

L'ho eseguito su un PC con Windows 10 64-bit Pro , compilando con MSVC 2019 Community Edition nella versione 16.8.1, utilizzando il sistema di compilazione cmake con quanto segueCMakeLists.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)

Ho letto che std::stringsi basano sul chartipo che è un singolo byte. Vedo che l'output del mio snippet indica che str(la std::stringvariabile) è codificata in UTF-8 . Ho letto che i compilatori Microsoft usano wchar_ts con 2 byte per creare std::wstrings (invece di 4 byte wchar_t, ad esempio GNU gcc) e quindi si aspetterebbero che wstr(la std::wstringvariabile) sia (qualsiasi tipo di) codificato UTF-16 . Ma non riesco a capire perché la "ß" (latina diesis s) sia codificata come invece 0x00c30178mi aspettavo 0x00df. Qualcuno possa dirmi:

  • Perché sta succedendo?
  • Come posso finire con i std::wstrings codificati UTF-16 (Big Endian andrebbe bene, non mi dispiace un BOM)? Probabilmente ho bisogno di dirlo al compilatore in qualche modo?
  • Che tipo di codifica è questa?

MODIFICA 1

cambiato il titolo, in quanto non si adattava correttamente alle domande (e in realtà UTF-8 e UTF-16 sono codifiche diverse, quindi io stesso nuovo la risposta già ...)

MODIFICA 2

dimenticavo di menzionare: io uso l' amd64obiettivo del compilatore citato

MODIFICA 3

se aggiungo il /utf-8flag come indicato nei commenti di dxiv (vedi il suo SO-Post collegato ), ottengo l'output desiderato

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

che assomiglia a UTF-16-BE (senza BOM) per me. Dato che ho avuto problemi con l'ordine corretto dei comandi cmake, questo è il mio CmakeLists.txtfile corrente . È importante mettere il add_compile_optionscomando prima del add_executablecomando (ho aggiunto l'avviso per comodità)

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)

Trovo il if-endifmodo più leggibile rispetto a quello della sintassi del generatore, ma anche la scrittura funzionerebbe.add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")

Nota: per Qt-Projects c'è una bella opzione per il .profile (vedi questo post Qt-Form )

win32 {
    QMAKE_CXXFLAGS += /utf-8
}

Ancora la prima parte della mia domanda è aperta: quale codifica è 0x00c30178per "ß" (latino diesis)?

Risposte

5 dxiv Dec 01 2020 at 16:41

Come chiarito nei commenti, il .cppfile sorgente è codificato in UTF-8. Senza un BOM e senza un'opzione esplicita /source-charset:utf-8, il compilatore Visual C ++ assume per impostazione predefinita che il file di origine viene salvato nella codifica tabella codici attiva. Dalla documentazione Set Source Character Set :

Per impostazione predefinita, Visual Studio rileva un indicatore di ordine dei byte per determinare se il file di origine è in un formato Unicode codificato, ad esempio UTF-16 o UTF-8. Se non viene trovato alcun contrassegno per l'ordine dei byte, si presume che il file di origine sia codificato utilizzando la tabella codici utente corrente, a meno che non si specifichi un nome di set di caratteri o una tabella codici utilizzando l'opzione / source-charset.

La codifica UTF-8 di äöüßis C3 A4 C3 B6 C3 BC C3 9F, e quindi la riga:

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

è visto dal compilatore come:

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

Supponendo che la tabella codici attiva sia il solito Windows-1252 , i caratteri (estesi) vengono mappati come:

    win-1252    char    unicode

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

Pertanto L"\xC3\xA4\xC3\xB6\xC3\xBC\xC3\x9F"viene tradotto in:

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

Per evitare tale (errata) traduzione, è necessario indicare a Visual C ++ che il file di origine è codificato come UTF-8 passando un'opzione del compilatore esplicita /source-charset:utf-8(o /utf-8). Per i progetti basati su CMake, questo può essere fatto utilizzando add_compile_optionscome mostrato in Possibile forzare CMake / MSVC a utilizzare la codifica UTF-8 per i file sorgente senza BOM? C4819 .

MarshallClow Nov 30 2020 at 20:50

quindi ci si aspetterebbe che wstr (la variabile std :: wstring) sia (qualsiasi tipo di) codificato UTF-16

std::wstringnon specifica una codifica. È una sequenza di "caratteri larghi", per alcuni tipi di caratteri larghi (che sono definiti dall'implementazione).

Ci sono sfaccettature di conversione definite nella libreria standard per la conversione in / da codifiche differenti.