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

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

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

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

Ответ 1

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

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

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

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

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

Ответ 2

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-разрядный) код кода для "Basic Multilingual Plane", что означает символы с кодовыми UI-кодом 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:

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

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

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

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

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

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

Объявленные типы: 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>.

Ответ 3

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

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

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

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

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

Ответ 4

Выполнение строгих Unicode-программирования:

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

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

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

Ответ 5

Самое главное - всегда делать четкое различие между текстовыми и двоичными данными. Попробуйте следовать модели Python 3.x str vs. bytes или SQL TEXT vs. 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 s. Некоторые полезные макросы в 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.

Ответ 6

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

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

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

Ответ 7

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

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

Ответ 8

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