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

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

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

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

8 ответов


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

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

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

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

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


C99 или ранее

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

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

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

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

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

UTF-8 хорошо известен как многобайтовый формат, с тщательной структурой, которая позволяет найти начало символов в строке надежно, начиная с любой точки строки. Однобайтовые символы имеют значение high-bit, равное нулю. Многобайтовые символы имеют первый символ, начинающийся с одного из битовых шаблонов 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-битное слово) код, установленный для "базовой многоязычной плоскости", что означает символы с кодом Юникода точки U+0000 .. U+FFFF, но использует две единицы (32 бита) для символов за пределами этого диапазона. Таким образом, код, который работает с кодировкой UTF-16, должен иметь возможность обрабатывать кодировки переменной ширины, как и UTF-8. Коды для символов двойной единицы называются суррогатами.

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

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

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

C11 и <uchar.h>

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

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

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

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

синтаксис
универсальный-характер-имя:
    \u hex-quad
    \U hex-quad hex-quad
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.) The <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-версией функций).

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

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


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

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

означает ли это, что мой код должен не использовать типы char нигде и что необходимо использовать функции, которые могут иметь дело с 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, а иногда его кодировка до Юникода в Восточной Азии. Я бы рекомендовал использовать typedefs

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


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

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

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


Я бы не доверял никакой стандартной реализации библиотеки. Просто сверните свои собственные типы 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;
}

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