Como const std :: wstring é codificado e como mudar para UTF-16

Nov 30 2020

Criei este mínimo de trabalho C ++ exemplo trecho para comparar bytes (por sua representação hex) em um std::stringe uma std::wstring, quando definindo uma cadeia com caracteres não ASCII alemão em qualquer um dos tipos.

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

A saída deste snippet é

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

Eu executei isso em um PC executando o próprio Windows 10 Pro de 64 bits , compilando com MSVC 2019 Community Edition na versão 16.8.1, usando o sistema de compilação cmake com o seguinteCMakeLists.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)

Eu li, que std::strings são baseados no chartipo que é um único byte. Vejo que a saída do meu snippet indica que str(a std::stringvariável) é codificada em UTF-8 . Eu li que os compiladores da Microsoft usam wchar_ts com 2 bytes para formar std::wstrings (em vez de 4 bytes, wchar_tpor exemplo, GNU gcc) e, portanto, esperariam que wstr(a std::wstringvariável) fosse (qualquer tipo de) codificado em UTF-16 . Mas não consigo entender por que o "ß" (s em sustenido em latim) está codificado como 0x00c30178eu esperava 0x00df. Alguém pode me dizer:

  • Porque isso está acontecendo?
  • Como posso terminar com std::wstrings codificados em UTF-16 (Big Endian seria bom, não me importo com um BOM)? Eu provavelmente preciso dizer ao compilador de alguma forma?
  • Que tipo de codificação é essa?

EDITAR 1

mudou o título, pois não se encaixava nas perguntas corretamente (e, na verdade, UTF-8 e UTF-16 são codificações diferentes, então eu já sei a resposta ...)

EDITAR 2

esqueci de mencionar: eu uso o amd64destino do compilador mencionado

EDITAR 3

se adicionar o /utf-8sinalizador como apontado nos comentários de dxiv (veja seu SO-Post vinculado ), obtenho a saída desejada

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

que se parece com UTF-16-BE (sem BOM) para mim. Como tive problemas com a ordem correta dos comandos cmake, este é o meu CmakeLists.txtarquivo atual . É importante colocar o add_compile_optionscomando antes do add_executablecomando (adicionei o Aviso por conveniência)

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)

Acho o if-endifcaminho mais legível do que o gerador de sintaxe, mas escrever também funcionaria.add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")

Nota: Para Qt-Projects, há uma boa opção para o .proarquivo (veja esta postagem do Qt-Form )

win32 {
    QMAKE_CXXFLAGS += /utf-8
}

Ainda assim, a primeira parte da minha pergunta está aberta: qual codificação é 0x00c30178para "ß" (latin sharp s)?

Respostas

5 dxiv Dec 01 2020 at 16:41

Conforme esclarecido nos comentários, o .cpparquivo de origem é codificado em UTF-8. Sem um BOM e sem uma /source-charset:utf-8opção explícita , o compilador Visual C ++ assume como padrão o arquivo de origem é salvo na codificação de página de código ativa. Da documentação do conjunto de caracteres de origem :

Por padrão, o Visual Studio detecta uma marca de ordem de byte para determinar se o arquivo de origem está em um formato Unicode codificado, por exemplo, UTF-16 ou UTF-8. Se nenhuma marca de ordem de byte for encontrada, ele assume que o arquivo de origem está codificado usando a página de código do usuário atual, a menos que você especifique um nome de conjunto de caracteres ou página de código usando a opção / source-charset.

A codificação UTF-8 de äöüßé C3 A4 C3 B6 C3 BC C3 9Fe, portanto, a linha:

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

é visto pelo compilador como:

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

Supondo que a página de código ativa seja o Windows-1252 usual , os caracteres (estendidos) mapeiam como:

    win-1252    char    unicode

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

Portanto, L"\xC3\xA4\xC3\xB6\xC3\xBC\xC3\x9F"é traduzido para:

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

Para evitar essa tradução (incorreta), o Visual C ++ precisa ser informado de que o arquivo de origem está codificado como UTF-8, passando uma opção explícita /source-charset:utf-8(ou /utf-8) do compilador. Para projetos baseados em CMake, isso pode ser feito usando add_compile_optionscomo mostrado em Possível forçar o CMake / MSVC a usar a codificação UTF-8 para arquivos de origem sem um BOM? C4819 .

MarshallClow Nov 30 2020 at 20:50

portanto, seria de se esperar que wstr (a variável std :: wstring) fosse (qualquer tipo de) codificado em UTF-16

std::wstringnão especifica uma codificação. É uma sequência de "caracteres largos", para algum tipo de caracteres largos (que são definidos pela implementação).

Existem facetas de conversão definidas na biblioteca padrão para a conversão de / para codificações diferentes.