Новые параметры компилятора Microsoft C/C++ для управления наборами символов


Автор: Джим Спринфилд.
Источник: New Options for Managing Character Sets in the Microsoft C/C++ Compiler.

Компилятор Microsoft C/C++ развивался вместе с DOS, 16-разрядной Windows и 32/64-разрядной Windows. Его поддержка различных наборов символов, кодовых страниц и Unicode также менялась всё это время. Эта запись объяснит, как наш компилятор работал раньше и расскажет о новых параметрах  компилятора C/C в Visual Studio 2015 Update 2 CTP в части поддержки файлов UTF-8 без BOM и управления наборами символов выполнения. Пожалуйста, скачайте его и попробуйте.  Сведения о других изменения компилятора в обновлении 2 приведены в этой записи.

Есть несколько прекрасных онлайн-ресурсов, подробно описывающих Unicode, DBCS, MBCS, кодовые страницы и другие вещи. Я не буду пытаться воспроизводить это и быстро дам общее представление. Сайт Unicode Consortium – хорошее место для подробного изучения Unicode.

Есть две основных момента для понимания того, как наш компилятор обрабатывает разные наборы символов. Во-первых, как он интерпретирует байты исходного файла (исходный набор символов); во-вторых, какие байты он записывает в двоичный файл (набор символов выполнения). Важно понимать, как исходный код закодирован и хранится на диске.

Явное указание кодировки Unicode

Стандартный способ указания файлов Unicode – использование метки порядка следования байт (BOM). Метка BOM может указывать на UTF-32, UTF-16 и UTF-8, а также на обратный или прямой порядок следования байт. Это указывается последовательностью байт, которая кодирует символ U+FEFF в нужной кодировке. UTF-8 кодируется как поток байт, для которого обычно нет “порядка” байт, но индикатор UTF-8 всё же обычно называется “BOM”.

Неявное указание кодировки

Во времена первых версий Windows (и DOS) до внедрения поддержки Unicode текстовые файлы хранились без указания кодировки. Приложение само решало, как интерпретировать содержимое файла. В DOS любой символ вне диапазона ASCII отображался встроенными средствами видеокарты. С точки зрения Windows это стало кодовой страницей OEM (437). Она включала некоторые символы, отличные от английскийх, а также символы рисования рамок.

Со временем в Windows появилась поддержка двухбайтовых (DBCS) и многобайтовых (MBCS) наборов символов. Стандартный способ указания кодировки текстового файла по-прежнему отсутствовал, и байты обычно интерпретировались с использованием текущей кодовой страницы системы. В 32-разрядной Windows появились API для UTF-16 и так называемые “ANSI” API, которые принимали 8-разрядные символы, интепретируя их с использованием текущей кодовой страницы системы.

Примечание. В Windows нельзя задать кодовую страницу Unicode (UTF-16 или UTF-8), поэтому в большинстве случаев нет простого способа заставить старое приложение понимать файлы в кодировке Unicode без BOM.

Сегодня также общепринято кодировать файлы в UTF-8 без BOM. По умолчанию так делается в большинстве окружений Linux. Хотя инструменты Linux могут обрабатывать BOM, большая их часть не генерирует эту метку. Отсутствие BOM фактически делает проще многие вещи, такие как объединение файлов или дополнение файла, не заставляя беспокоиться о том, кто должен записать BOM.

Как компилятор Microsoft C/C++ читает текст из файла

Некоторое время назад компилятор Microsoft стал использовать UTF-8 для внутренних целей. Файлы, считываемые с диска, на лету преобразовывались в UTF-8. Если файл содержит BOM, мы читаем файл, используя указанную кодировку и преобразуя её в UTF-8. Если файл не содержит BOM, мы пытаемся определить порядок следования байт кодировки UTF-16 по первым 8 байтам. Если кодировка файла похожа на UTF-16, мы обрабатываем файл так, как будто в нём есть BOM UTF-16.

Если BOM отсутствует и кодировка не похожа на UTF-16, мы используем текущую кодовую страницу (результат вызова функции GetACP) для преобразования байтов с диска в UTF-8. Это может быть правильно или неправильно в зависимости от фактической кодировки файла и от содержащихся в нём символов. Если файл действительно закодирован в UTF-8, обработка будет неверной, поскольку не бывает системной кодовой страницы P_UTF8.

Набор символов выполнения

Также важно понимать, что такое “набор символов выполнения”. В зависимости от набора символов выполнения компилятор по-разному интерпретирует строки. Давайте для начала рассмотрим простой пример.

const char ch = 'h';
const char u8ch = u8'h';
const wchar_t wch = L'h';
const char b[] = "h";
const char u8b[] = u8"h";
const wchar_t wb [] = L"h";

Вышеприведённый код интерпретируется так, как будто вы набрали следующее:

const char ch = 0x68;
const char u8ch = 0x68;
const wchar_t wch = 0x68;
const char b[] = {0x68, 0};
const char u8b[] = {0x68, 0};
const wchar_t wb [] = {0x68, 0};

Это должно быть очевидным и действует независимо от кодировки файла или текущей кодовой страницы. Теперь посмотрим на следующий код.

const char ch = '屰';
const char u8ch = u8'屰';
const wchar_t wch = L'屰';
const char b[] = "屰";
const char u8b[] = u8"屰";
const wchar_t wbuffer[] = L"屰";

Примечание. Я выбрал случайный символ, но оказалось, что символ Han, означающий “непослушный”, удовлетворяет моим намерениям. Это символ Unicode U+5C70.

Нам следует учесть несколько факторов. Как закодирован файл, содержащий данный код? Какова текущая кодовая страница системы, на которой мы компилируем код? В UTF-16 это код 0x5C70, в UTF-8 это последовательность байт 0xE5, 0xB1, 0xB0. В кодовой странице 936 это байты 0x8C, 0xDB. Данный символ невозможно представить в кодовой странице 1252 (Latin-1), с которой я работаю в данный момент. Кодовая страница 1252 обычно используется Windows в английском и других западных языках. В табл. 1 показаны результаты для различных кодировок файла при работе в системе с кодовой страницей 1252.

Таблица 1. Пример результатов компиляции кода с разными кодировками

Кодировка файла UTF-8 с BOM UTF-16LE с или без BOM UTF-8 без BOM DBCS (936)
Байты, представляющие 屰в исходном коде 0xE5, 0xB1, 0xB0 0x70, 0x5C 0xE5, 0xB1, 0xB0 0x8C, 0xDB
Преобразование UTF8 → UTF8 UTF16-LE → UTF-8 1252 → UTF8 1252 → UTF-8
Внутреннее представление (UTF-8) 0xE5, 0xB1, 0xB0 0xE5, 0xB1, 0xB0 0xC3, 0xA5, 0xC2, 0xB1, 0xC2, 0xB0 0xC5, 0x92, 0xC3, 0x9B
Преобразование в набор символов выполнения
char ch = ‘屰’;
UTF-8 → CP1252
0x3F* 0x3F* 0xB0 0xDB
char u8ch = u8’屰’;
UTF-8 → UTF-8
ошибка C2015 ошибка C2015 ошибка C2015 ошибка C2015
wchar_t wch = L’屰’;
UTF-8 → UTF-16LE
0x5C70 0x5C70 0x00E5 0x0152
char b[] = “屰”;
UTF-8 → CP1252
0x3F, 0* 0x3F, 0* 0xE5, 0xB1, 0xB0, 0 0x8C, 0xDB, 0
char u8b[] = u8″屰”;
UTF-8 → UTF-8
0xE5, 0xB1, 0xB0, 0 0xE5, 0xB1, 0xB0, 0 0xC3, 0xA5, 0xC2, 0xB1, 0xC2, 0xB0, 0 0xC5, 0x92, 0xC3, 0x9B, 0
wchar_t wb[] = L”屰”;
UTF-8 → UTF-16LE
0x5C70, 0 0x5C70, 0 0x00E5, 0x00B1, 0x00B0, 0 0x0152, 0x00DB, 0

* генерируется предупреждение C4566:“символ ‘\u5C70’ не может быть представлен в текущей кодовой странице (1252)”
Ошибка C2015: “константа содержит слишком много символов ”

В первом и втором столбцах мы знаем кодировку файла, поэтому внутреннее представление в UTF-8 верное: 0xE5, 0xB1, 0xB0. Однако набор символов выполнения – кодовая страница 1252, и после неудачной попытки преобразовать символ Unicode U+5C70 к этой кодовой странице используется символ замены по умолчанию 0x3F (вопросительный знак). Мы выдаём предупреждение C4566, но используем преобразованный символ 0x3F. Для символьной константы u8 уже используется UTF-8, и преобразование не требуется, но мы не можем сохранить три байта в один и выдаём ошибку C2015. Для расширенного набора символов выполнения используется кодировка UTF-16, поэтому символы и строки из расширенного набора символов преобразуются правильно. Для строковой константы u8 уже используется UTF-8, и преобразование не выполняется.

В третьем столбце (UTF-8 без BOM) на диске записаны символы 0xe5, 0xb1 и 0xb0. Каждый символ интерпретируется с использованием текущей кодовой страницы 1252 и преобразуется в UTF-8, превращаясь во внутреннюю последовательность трёх двухбайтовых символов UTF-8: (0xC3, 0xA5), (0xC2, 0xB1) и (0xC2, 0xB0). Для простых символьных присваиваний символы преобразуются обратно в кодовую страницу 1252: 0xE5, 0xB1, 0xB0. Это многосимвольная константа и результат тот же, что и при обнаружении компилятором константы ‘abcd’. Значение многосимвольной константы определяется реализацией; применительно к VC это целое число, где каждому байту соответствует один символ. В случае присвоения такого значения символу происходит преобразование и остаётся лишь младший байт. Для символьных констант u8 мы генерируем ошибку C2015, когда используется более одного байта. Примечание. Компилятор по-разному обрабатывает многосимвольные константы из обычного и расширенного наборов символов. Для расширенного набора символов мы берём первый символ многосимвольной константы, в данном случае 0x00E5. Для обычной строковой константы последовательность преобразуется в обратном направлении с использованием текущей кодовой страницы и превращается в четыре байта: 0xe5, 0xb1, 0xb0, 0. Строковая константа u8 использует такой же набор символов, как и внутреннее представление: 0xC3, 0xA5, 0xC2, 0xB1, 0xC2, 0xB0, 0. Для строки из расширенного набора символов мы используем UTF-16 в качестве набора символов выполнения: 0x00E5, 0x00B1, 0x00B2, 0.

Наконец, в четвёртом столбце у нас файл с кодовой страницей 936, где символы хранятся на диске в следующем виде: 0x8C, 0xDB. Мы преобразуем их, используя текущую кодовую страницу 1252 и получаем два двухбайтовых символа UTF-8: (0xC5, 0x92), (0xC3, 0x9B). Для однобайтового набора символов символы преобразуются к виду 0x8C, 0xDB, а символ получает значение 0xDB. Для символьной константы u8 символы не преобразуются, но возникает ошибка. Для символьной константы из расширенного набора символы преобразуются в UTF-16 к виду 0x0152, 0x00DB. Используется первое значение – 0x0152. Для строковых констант выполняются аналогичные преобразования.

Продолжение следует…

Реклама
Запись опубликована в рубрике перевод с метками , , , . Добавьте в закладки постоянную ссылку.

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s