C/C++ UTF chaos

MSVC, кодировка исходников и строковые литералы

MSVC предоставляет аж 5 (прописью: пять) вариантов кодировки UTF для сохранения исходников:

  • Нормальный человеческий UTF-8
  • UTF-8, но с BOM (byte order mark, который в случае UTF-8 не рекомендован к применению стандартом Unicode, от него одна головная боль)
  • Редкий зверь UTF-7 (он где-то кроме IMAP встречается?)
  • UTF-16 LE
  • UTF-16 BE

Сохранение исходников любом UTF, кроме UTF-8, не кросс-платформенно и поэтому не рассматривается. Остаются UTF-8 с и без BOM.

В коде литералы строк могут быть “однобайтные” (char) и “широкие” (wchar_t):

const char*    cs =  "Я";
const wchar_t* ws = L"Я";

Символ 'Я' в UTF-8 представляется как D0 AF, а в UTF-16 – как 042F.

Литерал На диске В памяти
BOM Без BOM
char* D0 AF DF 00 D0 AF 00
wchar_t* 042F 0000 0420 0407 0000

В случае с BOM, в char* один символ DF – это 'Я' в Windows-1251, т. е. MSVC конвертирует строку в системную ANSI-кодировку (очевидно, с потерями). Зато с wchar_t* все в порядке. Без BOM наоборот, в char* записано желаемое UTF-8 представление строки, но вот в wchar_t* – непонятные 0420 0407. Это UTF-16 представление символов РЇ, которые в Windows-1251 имеют коды D0 AF (сравните с исходным UTF-8).

То есть, корректность обработки юникодных строковых литералов зависит от параметров сохранения файла, а одновременное использование char* и wchar_t* в этом случае и вовсе невозможно:

  • При сохранении исходника в UTF-8 с BOM, будут испорчены char* (сконвертированы в “системный” ANSI).
  • При сохранении в UTF-8 без BOM, будут испорчены wchar_t* (сконвертированы в UTF-16, как будто исходник был в ANSI).