C Программирование: как программировать для Unicode?


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

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

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

8 76

8 ответов:

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

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

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

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

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

C99 или ранее

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

следовательно, подход, предложенный Хансом Ван Эком (который должен напишите обертку вокруг ICU-международных компонентов для Unicode - библиотеки) - это звук, ИМО.

wchar_t.

Unicode полностью представляет собой 21-битный формат. То есть Юникод резервирует кодовые точки от 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, конечно, может кодировать любую кодовую точку Юникода в одной единице хранения. Он эффективен для вычислений, но не для хранения.

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

C11 и <uchar.h>

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

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

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

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

синтаксис
universal-character-name:
    \u hex-quad
    \UХекс-шестигранный квад-квад
hex-quad:
    шестнадцатеричная-цифра шестнадцатеричная-цифра шестнадцатеричная-цифра шестнадцатеричная-цифра

7.28 коммунальные услуги Юникод <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()

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

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

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

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

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

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

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

чтобы сделать строгое Программирование Юникода:

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

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

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

самое главное-это всегда делают четкое различие между текстовыми и двоичными данными. Попробуйте следовать модели 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

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

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

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

Читайте также:

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

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

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

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

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

что еще более важно, избежать TCHAR.

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

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

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

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

#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;
}

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