¿Cómo se codifica const std :: wstring y cómo cambiar a UTF-16?

Nov 30 2020

He creado este mínimo de trabajo C ++ fragmento de ejemplo para comparar bytes (por su representación hexadecimal) en una std::stringy una std::wstringhora de definir una cadena con caracteres no ASCII alemán en uno u otro tipo.

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

El resultado de este fragmento es

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

Ejecuté esto en una PC con Windows 10 64-bit Pro , compilando con MSVC 2019 Community Edition en la versión 16.8.1, usando el sistema de compilación cmake con lo siguienteCMakeLists.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)

Leí que los std::stringmensajes de correo electrónico se basan en el chartipo, que es un solo byte. Veo que la salida de mi fragmento indica que str(la std::stringvariable) está codificada en UTF-8 . Leí que los compiladores de Microsoft usan wchar_ts con 2 bytes para componer std::wstrings (en lugar de 4 bytes wchar_tpor ej. GNU gcc) y por lo tanto esperarían que wstr(la std::wstringvariable) esté (cualquier tipo de) codificada en UTF-16 . Pero no puedo entender por qué la "ß" (s aguda latina) está codificada como 0x00c30178esperaba 0x00df. Que alguien me diga:

  • ¿Por qué está pasando esto?
  • ¿Cómo puedo terminar con std::wstrings codificados en UTF-16 (Big Endian estaría bien, no me importa una lista de materiales)? ¿Probablemente necesito decirle al compilador de alguna manera?
  • ¿Qué tipo de codificación es esta?

EDITAR 1

cambió el título, ya que no se ajustaba correctamente a las preguntas (y en realidad UTF-8 y UTF-16 son codificaciones diferentes, por lo que yo mismo conozco la respuesta ya ...)

EDITAR 2

olvidé mencionar: uso el amd64destino del compilador mencionado

EDITAR 3

si agrego la /utf-8bandera como se señala en los comentarios de dxiv (vea su SO-Post vinculado ), obtengo el resultado deseado

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

que se parece a UTF-16-BE (sin BOM) para mí. Como tuve problemas con el orden correcto de los comandos de cmake, este es mi CmakeLists.txtarchivo actual . Es importante poner el add_compile_optionscomando antes del add_executablecomando (agregué el Aviso por conveniencia)

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)

Encuentro la if-endifforma más legible que la de sintaxis del generador, pero la escritura también funcionaría.add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")

Nota: Para Qt-Projects hay un buen cambio para el .proarchivo (vea esta publicación de Qt-Form )

win32 {
    QMAKE_CXXFLAGS += /utf-8
}

Aún así, la primera parte de mi pregunta está abierta: ¿Qué codificación es 0x00c30178para "ß" (s sostenida latina)?

Respuestas

5 dxiv Dec 01 2020 at 16:41

Como se aclara en los comentarios, el .cpparchivo fuente está codificado en UTF-8. Sin una lista de materiales y sin un modificador explícito /source-charset:utf-8, el compilador de Visual C ++ adopta de forma predeterminada que el archivo de origen se guarda en la codificación de la página de códigos activa. De la documentación de Set Source Character Set :

De forma predeterminada, Visual Studio detecta una marca de orden de bytes para determinar si el archivo de origen está en un formato Unicode codificado, por ejemplo, UTF-16 o UTF-8. Si no se encuentra ninguna marca de orden de bytes, se supone que el archivo fuente está codificado usando la página de códigos de usuario actual, a menos que especifique un nombre de juego de caracteres o una página de códigos usando la opción / source-charset.

La codificación UTF-8 de äöüßes C3 A4 C3 B6 C3 BC C3 9F, y por lo tanto la línea:

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

es visto por el compilador como:

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

Suponiendo que la página de códigos activa sea el Windows-1252 habitual , los caracteres (extendidos) se asignan como:

    win-1252    char    unicode

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

Por lo tanto, L"\xC3\xA4\xC3\xB6\xC3\xBC\xC3\x9F"se traduce a:

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

Para evitar dicha (mala) traducción, Visual C ++ necesita que se le diga que el archivo fuente está codificado como UTF-8 pasando un modificador explícito /source-charset:utf-8(o /utf-8) del compilador. Para proyectos basados ​​en CMake, esto se puede hacer usando add_compile_optionscomo se muestra en ¿Es posible forzar a CMake / MSVC a usar la codificación UTF-8 para archivos de origen sin una lista de materiales? C4819 .

MarshallClow Nov 30 2020 at 20:50

por lo tanto, esperaría que wstr (la variable std :: wstring) tenga (cualquier tipo de) codificación UTF-16

std::wstringno especifica una codificación. Es una secuencia de "caracteres anchos", para algún tipo de caracteres anchos (que están definidos por la implementación).

Hay facetas de conversión definidas en la biblioteca estándar para convertir a / desde diferentes codificaciones.