вторник, 26 декабря 2023 г.

[prog.c++] Сочетание ключиков -source-charset:windows-1251 и -execution-charset:utf-8 в Visual C++

Не смотря на то, что в мире Linux-ов уже давным давно все на UTF-8, под Windows еще встречаются исходники с комментариями и не-юникодными строковыми/символьными литералами в CP1251. При этом Microsoft добавила в свой компилятор ключик -execution-charset, который указывает, в каком представлении строковые константы будут представлены в результирующем exe/dll файле. Соответственно, значение utf-8 для -execution-charset указывает, что в run-time ваши narrow string-и будут на самом деле в Unicode, в UTF-8 представлении. Как в Linux-е.

Но, если у вас narrow string литералы в исходнике в CP1251, то компилятору нужно указать это, чтобы он правильно сделал их конверсию в UTF-8. Для чего предназначен ключик -source-charset и значение windows-1251 для него.

Однако, указав VC++ компилятору и -source-charset:windows-1251, и -execution-charset:utf-8, можно сделать для себя удивительные открытия.

Вот, например, простая программа:

#include <iostream>
#include <typeinfo>

int main()
{
   const char str[]{ "Привет!" };
   std::cout << sizeof(str) << std::endl;
}

Когда мы ее компилируем только с -source-charset:windows-1251, то результат 8. Что вполне ожидаемо, т.к. в слове "Привет" шесть букв, плюс восклицательный знак, плюс финальный ноль-символ.

Но если мы добавим ключик -execution-charset:utf-8, то результатом будет уже 14.

Почему? Потому что каждый русский символ будет представлен уже двумя байтами в UTF-8, плюс восклицательный знак, плюс ноль-символ.

Но еще интереснее, если мы попытаемся задать массив char-ов посимвольно. Что-то вроде:

const char s[]{ 'П', 'р', 'и', 'в', 'е', 'т'};

Тут мы получим предупреждение компилятора об усечении значения при приведении его к типу char.

Но откуда это самое усечение возьмется?

А давайте посмотрим на это с помощью другой простенькой программы:

#include <iostream>
#include <typeinfo>

template<typename T>
char to_char(T v) noexcept
{
   using TU = std::make_unsigned_t<T>;

   std::cout << "type is: " << typeid(T).name() << ", value: 0x"
         << std::hex << static_cast<unsigned>(static_cast<TU>(v)) << std::dec << std::endl;
   return static_cast<char>(v);
}

void dump_hex(const std::string & w)
{
   std::cout << std::hex;
   for(const auto ch : w)
      std::cout << "0x" << static_cast<unsigned>(static_cast<unsigned char>(ch)) << ", ";
   std::cout << std::dec << std::endl;
}

int main()
{
   to_char( 'П' );
   dump_hex( "П" );
}

Если мы скомпилируем ее только с ключиком -source-charset:windows-1251, то получим:

type is: char, value: 0xcf
0xcf,

Т.е. вполне ожидаемо символьный литерал у нас имеет тип char.

А если добавим -execution-charset:utf-8, то:

type is: int, value: 0xd09f
0xd0, 0x9f,

Оказывается, что у символьного литерала уже тип int.

Что, в принципе, понятно. Но оказалось неожиданно :)


На правах саморекламы: изобретаю велосипеды для себя, могу изобретать и для вас.