Подтвердить что ты не робот

Юникодный ад (в Windows)

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

Пусть начнется я десять или два назад во времени, все было хорошо в мире. Я говорил с моим соседом, и он говорил на одном языке: просто английский. Мне, моему соседу и Windows, казалось, что мы сохранили нашу строку в 8-битном char, потому что все используемые символы могли быть сохранены в доступных 2 ^ 8 = 256 доступных комбинациях.

Тогда появилось чудесное интернет-сообщество и позволило мне поговорить с некоторыми друзьями в Европе (у которых не было времени изучать английский язык). Это затруднилось с нашим форматом char, количество используемых символов превысило 256, поэтому в нашем совершенно упрощенном видении мы решили использовать 16-битные wchar_t s. Кое-что называется Unicode UCS-2. Он имеет 2 ^ 16 = 65,536 доступных комбинаций, и этого должно быть достаточно для каждого языка в мире! Убедившись в нашей корректности, мы добавили 16-битные функции Windows API W, такие как MessageBoxW и CreateWindowW. Мы убедили каждого программиста в нашей религии и не поощряли использование злых 8-битных аналогов (MessageBoxA и CreateWindowA) и автоматически отображали вызов MessageBox на MessageBoxW, определяя _UNICODE в наших сборках. Поэтому мы должны также использовать функции wcs вместо старых str функций (например, strlen теперь должен быть wcslen или использовать автоматически отображаемый _tcslen).

Тогда все стало плохо, оказалось, что в мире есть другие люди, которые использовали даже более странные глифы (без обид), чем наши: японцы, китайцы и т.д. Это плохо, потому что, например, у китайцев более 70 000 разных персонажей. Много ругательств произошло и оставило нам новый тип юникода: UTF-16. Он также использует 16-битный тип данных, но некоторые символы требуют двух 16-битных значений (называемых суррогатной парой). Это означает, что мы не можем использовать индексы для этих 16-битных строк (например, строка [4] может не возвращать 5-й символ). Для исправления Windows API было решено, что все функции W теперь должны поддерживать формат UTF-16, это было простое решение, так как все старые строки UCS-2 были действительными строками UTF-16. Однако, поскольку мы храбрые программисты, теперь мы используем функции wcs. К сожалению, эти функции не являются суррогатными и все еще соответствуют формату UCS-2...

Тем временем на темном чердаке была разработана еще одна компактная форма юникода: UTF-8. Используя 8-битный тип данных, большинство западных языков могут храниться в одном 8-битном значении, как и в старые времена. Когда хранится более экзотический глиф, используются несколько 8-битных значений, для большинства европейских языков 2 достаточно. Однако он может расширить 4 из этих значений, по существу создавая 32-разрядный тип хранилища. Так же, как это толстый брат UTF-16, мы не можем использовать индексы для этих строк. Из-за этого более компактный формат UTF-8 теперь широко используется повсюду в Интернете, поскольку он экономит полосу пропускания.

Хорошо, вы сделали это через мою длинную рецензию:) Теперь у меня есть несколько вопросов/точек интереса:

  • Хорошо, я доволен использованием UTF-8 для хранения. Когда я читаю файл (с диска или ответа HTTP), я обнаруживаю подпись UTF-8 "\xEF\xBB\xBF" и помещаю содержимое через MultiByteToWideChar, которое оставляет меня с помощью строки UTF-16. Я могу использовать это с функциями API W, без проблем. Но теперь я хочу изменить строку, заменить некоторые символы и т.д. Старые добрые функции wcs больше не полезны, какие функции основной строки имеют UTF-16? Или есть какая-то великолепная библиотека, которую я не знаю? Изменить: похоже, ICU - довольно хорошее решение. Я также обнаружил, что функции wcs не совсем бесполезны, вы можете, например, использовать wcsstr для поиска, по существу просто сравнивает wchar_t s. Единственная проблема - длина строки.

  • У вас нет чувства, что уродливая ошибка была сделана, когда мы были вынуждены использовать 16-битные функции с дефицитом W. Разве проблема не была распознана на гораздо более ранней стадии, и пусть все исходные функции API берут строки UTF-8 и включают правильные подпрограммы строковой манипуляции? Или это уже возможно, и я ужасно ошибаюсь? Изменить: может быть, это был глупый вопрос, задним числом действительно замечательно, не нужно никому прикладывать кого-либо прямо сейчас;)

  • Для быстрого доступа индекса к символам мы должны хранить строки в 32-битных значениях. Это распространено? (Я слышу, как вы думаете: а затем мы сталкиваемся с внеземным языком, требующим больше комбинаций, и забава начинается снова и снова). Кажется, что недостатком этого подхода является то, что мы должны преобразовать строку обратно в UTF-16 каждый раз, когда мы вызываем вызовы Windows API. Изменить: просто, чтобы процитировать Альфа П. Штайнбаха, один символ за индекс - это безнадежный сон, я вижу это сейчас. Одна вещь, которую я полностью пропустил, была diacritics. Я также думаю, что хорошо обрабатывать в собственной кодировке ОС (для Windows UTF-16). Хотя UTF-8 был бы лучшим выбором, мы застряли в UTF-16 сейчас, нет смысла конвертировать назад и вперед между вашим кодом и API. Как предложено ниже, я буду отслеживать части в строке самими указателями вместо количества символов.

Я думаю, что ты заслужил себя прекрасным чаем чая, борющегося за этот длинный вопрос, иди до него, прежде чем ответить;)

Изменить: я согласен с тем, что мой вопрос закрыт, это будет лучше подходит для сообщения в блоге, но опять же я не пишу блог. Я думаю, что эта кодировка символов важна и должна быть следующей темой в любой книге программирования после простого примера приветствия! Публикация здесь привлекает внимание многих экспертов, эти люди не читают какой-либо случайный блог, и я высоко ценю их мнение. Поэтому благодарим всех за вклад.

4b9b3361

Ответ 1

По сильным предпочтениям вы должны переводить с UTF- * на UCS-4 при чтении данных. Вся ваша обработка должна быть выполнена на UCS-4, а затем (при необходимости) перевести обратно в UTF- * во время выхода.

Это все еще не исправляет все. Там есть набор "комбинаций диакритических" меток, а это означает, что даже если вы используете UCS-4, string[N] не обязательно соответствует символу N th строки. Существуют трансформации к каноническим формам, которые пытаются помочь с этим, но они не всегда могут выполнять эту работу, поэтому, если это действительно важно (для вашего приложения), вам просто нужно пройти через строку, разделить ее на единицы, каждая из которых представляют полный символ (базовый символ + и комбинирование диакритических знаков) и обрабатывают каждую из них как единицу.

Ответ 2

  • ICU - отличная строковая библиотека Unicode. Общая концепция с обработкой строк заключается в анализе любых внешних форм в памяти, так что каждое значение представляет собой полную кодовую точку, а не часть ее, например, с UTF-16 и UTF-8. Затем, после любой обработки, на выходе из программы, сериализуйте строку обратно в подходящий формат преобразования. Хотя основы легки, постарайтесь не сворачивать свою собственную библиотеку Unicode - вещи, такие как сортировка, поиск и другие сложные вопросы, лучше всего оставить в зрелой библиотеке.

  • Самолеты вне БМП не использовались и не определялись, поскольку потребность не была замечена. Конечно, как вы указали, безусловно, есть необходимость.

  • Да, это обычное дело, и, как уже упоминалось, это лучший способ сделать что-то, потому что он значительно улучшает почти все операции с строкой.

Ответ 3

Я принимаю решение:

  • Для внешнего интерфейса (файлы, аргументы командной строки, переменные среды, stdin/out) используют UTF-8, потому что поток байтов и весь язык C и С++ разрабатываются вокруг взаимодействия с окружением через байт потоки. В наиболее разумных файловых системах имена файлов также являются строками байтов с нулевым завершением.

  • Для простого прокрутки вы также можете сохранить строки в UTF-8, используя char* и т.д., и простые "" строковые литералы или новые литералы u8"" UTF-8.

  • Для текстовых манипуляций внутренне преобразуйте строку в UTC-4/UTF-32 и рассматривайте ее как массив char32_t. Это единственный разумный способ говорить о потоке символов.

  • UTF-16 была огромной ошибкой, ее нужно было расстрелять и избегать. См. здесь (я где-то там комментировал) и, возможно, здесь и .

Ответ 4

  • ICU - Международные компоненты для Юникода. Для правильных разрывов слов и отображения Windows включает Uniscribe, а не Windows использует FreeType (исправьте меня, если я ошибаюсь).

  • Да, да. Но, насколько я знаю, в то время, когда они принимали это решение, utf-32 не существовало, и они думали, что 65536 кодовых пунктов "будет достаточно для всех".

  • Нет, нет. Помимо использования памяти в четыре раза, проблема намного хуже, чем вы думаете. Вы не можете просто "изменить строку" и "заменить некоторые символы": даже при использовании 32-битных значений, поскольку один символ юникода не обязательно означает одну написанную букву или один символ, который вы можете удалить или заменить чем-то другим и не надеяться на что-либо брейки. Чтобы работать с текстом должным образом, вам все равно придется использовать что-то вроде ICU, поэтому нет никакой разницы между использованием utf-8 и utf-32, я думаю.

Ответ 5

Я не знаю, что вы имеете в виду о функциях wcs, которые не подходят. Почему бы и нет?

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

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

Что касается UTF-32, вряд ли какое-либо программное обеспечение использует это внутри. Я бы не рекомендовал его, особенно не на платформе, которая не поддерживает его вообще. Использование UTF-32 просто создаст работу для вас.

Ответ 6

Там ничего не мешает вам создать простой кеш, который хранит местоположение и длину байта кодированного кода UTF, чтобы вы могли фактически использовать произвольный доступ. Весь старый материал C, о котором вы говорите, вряд ли поможет, хотя.

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