Программирование на C: Как программировать для Unicode? - PullRequest
80 голосов
/ 09 февраля 2009

Какие предпосылки необходимы для строгого программирования Unicode?

Означает ли это, что мой код не должен нигде использовать типы char и что необходимо использовать функции, которые могут работать с wint_t и wchar_t?

А какую роль играют последовательности многобайтовых символов в этом сценарии?

Ответы [ 8 ]

37 голосов
/ 09 февраля 2009

C99 или ранее

Стандарт C (C99) предусматривает широкие символы и многобайтовые символы, но, поскольку нет гарантии, что эти широкие символы могут содержать, их значение несколько ограничено. Для данной реализации они предоставляют полезную поддержку, но если ваш код должен иметь возможность перемещаться между реализациями, нет достаточной гарантии, что они будут полезны.

Следовательно, подход, предложенный Хансом ван Экком (который должен написать обертку вокруг ICU - International Components for Unicode - library), является здравым, IMO.

Кодировка UTF-8 имеет много достоинств, одним из которых является то, что если вы не будете связываться с данными (например, обрезая их), то они могут быть скопированы функциями, которые не в полной мере осведомлены о тонкостях Кодировка UTF-8. Это категорически не так с wchar_t.

Юникод в полном объеме - это 21-битный формат. То есть Unicode резервирует кодовые точки от U + 0000 до U + 10FFFF.

Одна из полезных особенностей форматов UTF-8, UTF-16 и UTF-32 (где UTF обозначает формат преобразования Unicode - см. Unicode ) заключается в том, что вы можете выполнять преобразование между тремя представлениями без потеря информации. Каждый может представлять все, что могут представлять другие. UTF-8 и UTF-16 являются многобайтовыми форматами.

UTF-8, как известно, является многобайтовым форматом с тщательно продуманной структурой, которая позволяет надежно находить начало символов в строке, начиная с любой точки в строке. Однобайтовые символы имеют старший бит, установленный в ноль. Многобайтовые символы имеют первый символ, начинающийся с одной из битовых комбинаций 110, 1110 или 11110 (для 2-байтовых, 3-байтовых или 4-байтовых символов), с последующими байтами, всегда начинающимися 10. Символы продолжения всегда находятся в диапазон 0x80 .. 0xBF. Существуют правила, согласно которым символы UTF-8 должны быть представлены в минимально возможном формате. Одним из следствий этих правил является то, что байты 0xC0 и 0xC1 (также 0xF5..0xFF) не могут появляться в действительных данных UTF-8.

 U+0000 ..   U+007F  1 byte   0xxx xxxx
 U+0080 ..   U+07FF  2 bytes  110x xxxx   10xx xxxx
 U+0800 ..   U+FFFF  3 bytes  1110 xxxx   10xx xxxx   10xx xxxx
U+10000 .. U+10FFFF  4 bytes  1111 0xxx   10xx xxxx   10xx xxxx   10xx xxxx

Первоначально надеялись, что Unicode будет 16-битным кодовым набором, и все будет соответствовать 16-битному кодовому пространству. К сожалению, реальный мир более сложен, и его пришлось расширить до текущей 21-битной кодировки.

UTF-16, таким образом, представляет собой единичный код (16-разрядное слово), установленный для «Базовой многоязычной плоскости», то есть символов с кодовыми точками Unicode U + 0000 .. U + FFFF, но использует две единицы (32 биты) для символов за пределами этого диапазона. Таким образом, код, который работает с кодировкой UTF-16, должен быть способен обрабатывать кодировки с переменной шириной, как и UTF-8. Коды для двойных символов называются суррогатами.

Суррогаты - это кодовые точки из двух специальных диапазонов значений Unicode, зарезервированные для использования в качестве начальных и конечных значений парных кодовых единиц в UTF-16. Ведущие, также называемые высокими, суррогаты от U + D800 до U + DBFF, а конечные или низкие суррогаты от U + DC00 до U + DFFF. Их называют суррогатами, поскольку они не представляют символы непосредственно, а только в виде пары.

UTF-32, конечно, может кодировать любую кодовую точку Unicode в одной единице памяти. Он эффективен для вычислений, но не для хранения.

Вы можете найти гораздо больше информации на веб-сайтах ICU и Unicode.

C11 и <uchar.h>

Стандарт C11 изменил правила, но не все реализации догнали эти изменения даже сейчас (середина 2017 года). Стандарт C11 суммирует изменения для поддержки Unicode следующим образом:

  • Unicode-символы и строки (<uchar.h>) (изначально указано в ISO / IEC TR 19769: 2004)

Ниже приведен минимальный набросок функциональности. Спецификация включает в себя:

6.4.3 Имена универсальных символов

Синтаксис
универсальный характер имя:
\u шестнадцатеричный
\U hex-quad hex-quad
шестигранной четырехъядерный:
шестнадцатеричная цифра шестнадцатеричная цифра шестнадцатеричная цифра шестнадцатеричная цифра

7.28 Утилиты Unicode <uchar.h>

Заголовок <uchar.h> объявляет типы и функции для работы с символами Юникода.

Заявленные типы: mbstate_t (описано в 7.29.1) и size_t (описано в 7.19);

char16_t

- это целочисленный тип без знака, используемый для 16-разрядных символов, и тот же тип, что и uint_least16_t (описано в 7.20.1.2); и

char32_t

- это целое число без знака, используемое для 32-разрядных символов, и тот же тип, что и uint_least32_t (также описано в 7.20.1.2).

(Перевод перекрестных ссылок: <stddef.h> определяет size_t, <wchar.h> определяет mbstate_t, и <stdint.h> определяет uint_least16_t и uint_least32_t.) Заголовок <uchar.h> также определяет минимальный набор (перезапускаемых) функций преобразования:

  • mbrtoc16()
  • c16rtomb()
  • mbrtoc32()
  • c32rtomb()

Существуют правила, относительно которых символы Unicode могут использоваться в идентификаторах с использованием обозначений \unnnn или \U00nnnnnn. Возможно, вам придется активно активировать поддержку таких символов в идентификаторах. Например, GCC требует -fextended-identifiers, чтобы разрешить это в идентификаторах.

Обратите внимание, что macOS Sierra (10.12.5), если назвать только одну платформу, не поддерживает <uchar.h>.

21 голосов
/ 09 февраля 2009

Обратите внимание, что речь идет не о "строгом программировании Юникода" как таковом, а о некотором практическом опыте.

В моей компании мы создали библиотеку-оболочку для библиотеки ICU IBM. Библиотека-оболочка имеет интерфейс UTF-8 и преобразуется в UTF-16, когда необходимо вызвать ICU. В нашем случае мы не слишком беспокоились о снижении производительности. Когда производительность была проблемой, мы также поставляли интерфейсы UTF-16 (используя наш собственный тип данных).

Приложения могут оставаться в основном как есть (используя char), хотя в некоторых случаях им необходимо знать о некоторых проблемах. Например, вместо strncpy () мы используем оболочку, которая избегает обрезки последовательностей UTF-8. В нашем случае этого достаточно, но можно также рассмотреть возможность проверки сочетания символов. У нас также есть оболочки для подсчета количества кодовых точек, количества графем и т. Д.

При взаимодействии с другими системами нам иногда требуется выполнить пользовательскую композицию символов, поэтому вам может потребоваться некоторая гибкость (в зависимости от вашего приложения).

Мы не используем wchar_t. Использование ICU позволяет избежать непредвиденных проблем с переносимостью (но, разумеется, никаких других непредвиденных проблем: -).

10 голосов
/ 09 февраля 2009

Этот FAQ - это огромное количество информации. Между этой страницей и этой статьей Джоэла Спольски у вас будет хорошее начало.

Один вывод, к которому я пришел по пути:

  • wchar_t - 16 бит в Windows, но не обязательно 16 бит на других платформах. Я думаю, что это неизбежное зло в Windows, но, вероятно, его можно избежать в другом месте. Причина, по которой это важно в Windows, заключается в том, что он необходим для использования файлов, в имени которых есть символы, отличные от ASCII (вместе с версией функций W).

  • Обратите внимание, что API-интерфейсам Windows, которые принимают строки wchar_t, требуется кодировка UTF-16. Обратите внимание, что это отличается от UCS-2. Обратите внимание на суррогатные пары. На этой тестовой странице есть просветляющие тесты.

  • Если вы программируете в Windows, вы не можете использовать fopen(), fread(), fwrite() и т. Д., Поскольку они принимают только char * и не понимают кодировку UTF-8. Делает переносимость болезненной.

7 голосов
/ 09 февраля 2009

Для строгого программирования Unicode:

  • Используйте только строковые API, поддерживающие Unicode ( NOT strlen, strcpy, ... но их самые широкие аналоги wstrlen, wsstrcpy, ...)
  • При работе с блоком текста используйте кодировку, которая позволяет хранить символы Unicode (utf-7, utf-8, utf-16, ucs-2, ...) без потерь.
  • Убедитесь, что ваш набор символов по умолчанию для ОС совместим с Unicode (например: utf-8)
  • Использовать шрифты, совместимые с Юникодом (например, arial_unicode)

Многобайтовые последовательности символов - это кодировка, которая предшествует кодировке UTF-16 (которая обычно используется с wchar_t), и мне кажется, что это скорее только для Windows.

Я никогда не слышал о wint_t.

3 голосов
/ 18 августа 2010

Самое важное - всегда проводить четкое различие между текстовыми и двоичными данными . Попробуйте следовать модели Python 3.x str против bytes или SQL TEXT против BLOB.

К сожалению, C путает проблему, используя char как для "символа ASCII", так и для int_least8_t. Вы хотите сделать что-то вроде:

typedef char UTF8; // for code units of UTF-8 strings
typedef unsigned char BYTE; // for binary data

Возможно, вы также захотите использовать typedef для кодовых блоков UTF-16 и UTF-32, но это сложнее, поскольку кодировка wchar_t не определена. Вам понадобится просто препроцессор #if с. Вот некоторые полезные макросы в C и C ++ 0x:

  • __STDC_UTF_16__ & mdash; Если определено, тип _Char16_t существует и имеет вид UTF-16.
  • __STDC_UTF_32__ & mdash; Если определено, тип _Char32_t существует и имеет код UTF-32.
  • __STDC_ISO_10646__ & mdash; Если определено, то wchar_t - это UTF-32.
  • _WIN32 & mdash; В Windows wchar_t - это UTF-16, хотя это нарушает стандарт.
  • WCHAR_MAX & mdash; Может использоваться для определения размера wchar_t, но не для определения того, использует ли ОС его для представления Unicode.

Означает ли это, что мой код должен не использовать типы символов в любом месте, и что необходимо использовать функции, которые могут иметь дело с wint_t и wchar_t?

Смотри также:

Нет. UTF-8 - это совершенно корректная кодировка Unicode, которая использует строки char*. Преимущество состоит в том, что если ваша программа прозрачна для байтов, не относящихся к ASCII (например, преобразователь конца строки, который действует на \r и \n, но проходит через другие символы без изменений), вам не нужно будет вносить никаких изменений !

Если вы используете UTF-8, вам нужно изменить все предположения, что char = символ (например, не вызывайте toupper в цикле) или char = экранный столбец (например, для переноса текста).

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

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

Я бы рекомендовал активно избегать wchar_t, потому что он не кроссплатформенный: иногда это UTF-32, иногда UTF-16, а иногда - восточноазиатская кодировка до Unicode. Я бы рекомендовал использовать typedefs

Еще важнее, избегать TCHAR.

2 голосов
/ 29 марта 2017

Я бы не стал доверять любой стандартной реализации библиотеки Просто сверните свои собственные типы Unicode.

#include <windows.h>

typedef unsigned char utf8_t;
typedef unsigned short utf16_t;
typedef unsigned long utf32_t;

int main ( int argc, char *argv[] )
{
  int msgBoxId;
  utf16_t lpText[] = { 0x03B1, 0x0009, 0x03B2, 0x0009, 0x03B3, 0x0009, 0x03B4, 0x0000 };
  utf16_t lpCaption[] = L"Greek Characters";
  unsigned int uType = MB_OK;
  msgBoxId = MessageBoxW( NULL, lpText, lpCaption, uType );
  return 0;
}
2 голосов
/ 09 февраля 2009

Вы в основном хотите иметь дело со строками в памяти как массивами wchar_t вместо char. Когда вы выполняете любые операции ввода-вывода (например, чтение / запись файлов), вы можете кодировать / декодировать, используя UTF-8 (это, вероятно, наиболее распространенная кодировка), который достаточно прост для реализации. Просто погуглите RFC. Так что в памяти ничего не должно быть многобайтовым. Один wchar_t представляет один символ. Однако когда вы начинаете сериализацию, вам нужно кодировать что-то вроде UTF-8, где некоторые символы представлены несколькими байтами.

Вам также придется писать новые версии strcmp и т. Д. Для строк широких символов, но это не является большой проблемой. Самой большой проблемой будет взаимодействие с библиотеками / существующим кодом, которые принимают только массивы символов.

И когда дело доходит до sizeof (wchar_t) (вам нужно 4 байта, если вы хотите сделать это правильно), вы всегда можете переопределить его к большему размеру с помощью typedef / macro hacks, если вам нужно.

1 голос
/ 09 февраля 2009

Насколько я знаю, wchar_t зависит от реализации (как видно из этой вики-статьи ). И это не Юникод.

...