Печальная Судьба Спецификаторов Формата Функции Printf Для Символов Юникода В Visual C++

Поддержка Unicode в Windows появилась раньше, чем в большинстве других операционных систем.

Из-за этого многие проблемы, связанные с представлением символов, решались в Windows иначе, чем в других системах, разработчики которых отложили внедрение нового стандарта до лучших времен [1].

Самый важный пример: Windows использует кодировку UCS-2 для представления символов Юникода.

Он был рекомендован Консорциумом Unicode, поскольку версия 1.0 поддерживала только 65 536 символов [2].

Спустя пять лет Консорциум передумал, но к тому времени было уже поздно что-либо менять в Windows, поскольку на рынок уже были выпущены Win32s, Windows NT 3.1, Windows NT 3.5, Windows NT 3.51 и Windows 95 — все в них использовалась кодировка UCS -2 [3].

Но сегодня мы поговорим о строках формата функций.

печать .

Поскольку Unicode был принят в Windows до C, это означало, что разработчикам Microsoft пришлось придумать, как реализовать поддержку этого стандарта во время выполнения C. В результате такие функции, как wcscmp , вкшр И wprintf .

А как насчет строк формата в печать , то для них были введены следующие спецификаторы:

  • %s представляет строку той же ширины, что и строка формата;
  • представляет строку с шириной, обратной ширине строки формата;
  • %хс представляет обычную строку независимо от ширины строки формата;
  • %ws И %ls представляют широкую строку независимо от ширины строки формата.

Идея заключалась в том, что вы могли бы написать такой код:
  
  
  
  
  
   

TCHAR buffer[256]; GetSomeString(buffer, 256); _tprintf(TEXT("The string is %s.\n"), buffer);

И при компиляции в режиме ANSI вы получите такой результат:

char buffer[256]; GetSomeStringA(buffer, 256); printf("The string is %s.\n", buffer);

А при компиляции в режиме Unicode это выглядит так [4]:

wchar_t buffer[256]; GetSomeStringW(buffer, 256); wprintf(L"The string is %s.\n", buffer);

Поскольку спецификатор %s принимает строку той же ширины, что и строка формата, этот код будет корректно работать как в форматах ANSI, так и в Unicode. Это решение также существенно упрощает преобразование уже написанного кода из формата ANSI в формат Unicode, поскольку вместо спецификатора %s вставляется линия необходимой ширины.

Когда поддержка Unicode была официально добавлена в C99, комитет по стандартам языка C принял другую модель строки формата для этой функции.

печать :

  • %s И %хс представляют собой обычную строку;
  • %ls представляет собой широкую строку.

Вот здесь и начались проблемы.

За последние шесть лет для Windows было написано огромное количество программ объемом в миллиард строк, и они использовали старый формат. А как насчет компиляторов Visual C и C++? Было решено остаться со старой, нестандартной моделью, чтобы не сломать все существующие в мире программы Windows. Если вы хотите, чтобы ваш код работал в средах выполнения, которые следуют классическим правилам печать , а в тех, что следуют правилам стандарта C, придется ограничиться спецификаторами %хс для обычных строк и %ls для широких.

В этом случае гарантируется согласованность результатов независимо от того, передается ли в функцию строка формата.

спринтф или wsprintf .



#ifdef UNICODE #define TSTRINGWIDTH TEXT("l") #else #define TSTRINGWIDTH TEXT("h") #endif TCHAR buffer[256]; GetSomeString(buffer, 256); _tprintf(TEXT("The string is %") TSTRINGWIDTH TEXT("s\n"), buffer); char buffer[256]; GetSomeStringA(buffer, 256); printf("The string is %hs\n", buffer); wchar_t buffer[256]; GetSomeStringW(buffer, 256); wprintf("The string is %ls\n", buffer);

Раздельное определение ТСТРИНГВИДТ позволяет написать, например, такой код:

_tprintf(TEXT("The string is ") TSTRINGWIDTH TEXT("s\n"), buffer);

Поскольку людям нравится табличное представление информации, вот таблица.



Печальная судьба спецификаторов формата функции printf для символов Юникода в Visual C++

Я выделил строки со спецификаторами, которые определяются в C так же, как и в классическом формате Windows [5].

Используйте эти спецификаторы, если вы хотите, чтобы ваш код давал одинаковые результаты в обоих форматах.

Примечания [1] Казалось бы, внедрение Unicode в Windows раньше других систем должно было дать Microsoft преимущество первопроходца, но — по крайней мере в случае с Unicode — это оказалось для них «проклятием первопроходца», потому что остальные решили просто дождаться лучших времен, когда станут доступны более перспективные решения (например, кодировка UTF-8), и только потом внедрять Unicode в свои системы.

[2] Видимо считали, что 65 536 символов должно было хватить на всех .

[3] Позже он был заменен на UTF-16. К счастью, UTF-16 обратно совместим с UCS-2 для тех символов, которые могут быть представлены в обеих кодировках.

[4] Формально версия Unicode должна выглядеть так:

unsigned short buffer[256]; GetSomeStringW(buffer, 256); wprintf(L"The string is %s.\n", buffer);

Это факт wchar_t на тот момент это еще не был самостоятельный тип, и пока его не добавили в стандарт, он был всего лишь синонимом беззнаковый короткий .

О перипетиях судьбы wchar_t можно прочитать в отдельная статья .

[5] Классический формат, разработанный в Windows, появился первым, поэтому стандарту C пришлось адаптироваться к нему, а не наоборот. Примечание переводчика Я благодарен автору за эту публикацию.

Теперь становится понятно, как произошла вся эта путаница с "%s".

Дело в том, что наши пользователи постоянно задавались вопросом, почему PVS-Studio по-разному реагирует на, как им кажется, «переносимый» код, в зависимости от того, под Linux или под Windows они собирают свой проект. Необходимо сделать в описании диагностики В576 Этой теме посвящен специальный отдельный раздел (см.

«Широкие линии»).

После этой статьи все становится еще более ясным и очевидным.

Думаю, эту заметку стоит прочитать всем, кто занимается разработкой кроссплатформенных приложений.

Прочтите и расскажите своим коллегам.

Теги: #Разработка для Windows #C++ #Visual Studio #printf #si #история #история #visual c++ #Unicode #переносимость кода

Вместе с данным постом часто просматривают: