Почему POSIX указывает, что wctomb не безопасен для потоков, а не mbtowc? - PullRequest
17 голосов
/ 24 января 2011

В XSH 2.9.1 , wctomb указана как одна из функций, которая не обязана быть поточно-ориентированной. Однако противоположная функция преобразования mbtowc не отображается в списке. В реализации с кодировками, которые используют сдвиговые состояния, ни один не имеет поточно-ориентированного API, и нет никакого смысла в том, что одно должно быть поточно-безопасным, а другое - нет, в то время как ни одно не может быть поточно-безопасным без запрета кодировок с сохранением состояния.

Аналогично для wcstombs (который находится в списке) и mbstowcs (который отсутствует). Поскольку обе эти функции работают с целыми строками, которые начинаются и заканчиваются в начальном состоянии сдвига, они не имеют состояния, их API-интерфейсы являются поточно-ориентированными, и, опять же, не имеет смысла указывать одно направление как поточно-ориентированное, но не другой.

Кто-нибудь может пролить свет на это?

Ответы [ 4 ]

3 голосов
/ 13 марта 2012

Как вы отметили в вопросе, wctomb имеет или, по крайней мере, может иметь (скрытое) "состояние сдвига": см. http://pubs.opengroup.org/onlinepubs/009695399/functions/wctomb.html и сравните это с wcrtomb: http://pubs.opengroup.org/onlinepubs/009695399/functions/wcrtomb.html, который имеет явный указатель "состояния".

POSIX в основном позволяет программисту реализовать wctomb как вызов wcrtomb, используя переменную static для хранения состояния сдвига.Если эта переменная не является элементом для каждого потока, она не будет потокобезопасной.

(все это довольно очевидно и содержится в вашем вопросе, я просто повторю это для ясности здесь)

Обратите внимание, что никакой аргумент wctomb не дает вам какого-либо явного контроля над скрытым состоянием сдвига (если оно есть).В частности, вы не можете сбросить его в исходное состояние.

Но теперь посмотрите на mbtowc: http://pubs.opengroup.org/onlinepubs/009695399/functions/mbtowc.html, где в тексте написано:

Для кодирования, зависящего от состояния, эта функция переводится в исходное состояние с помощью вызова, для которого аргумент указателя символа s является нулевым указателем.Последующие вызовы с s, отличным от нулевого указателя, должны привести к необходимости изменения внутреннего состояния функции.Вызов с s в качестве нулевого указателя должен заставить эту функцию возвращать ненулевое значение, если кодировки имеют зависимость от состояния, и 0 в противном случае.Если реализация использует специальные байты для изменения состояния сдвига, эти байты не должны генерировать отдельные коды широких символов, но должны быть сгруппированы со смежным символом.

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

Хотя я не могу клясться в этом, я думаю, именно поэтому mbtowc не указан в спискекак не являющийся потокобезопасным.

(Это не тот способ, которым я написал бы текст в стандарте, хотя! Впрочем, если бы у меня был свой путь, многие из этих функций даже не существовали бы. :-))

2 голосов
/ 20 марта 2012

Фундаментальная асимметрия заключается в том, что ISO C требует, чтобы широкие символы имели фиксированную ширину (одинаковую для всех символов) и чтобы кодировка не имела состояний сдвига. Напротив, многобайтовое кодирование зависит от локали и может иметь различную ширину символов, а также состояния сдвига.

Все четыре функции имеют внутреннее состояние, сохраняемое между вызовами (mbstowcs и wcstombs также должны это делать, поскольку они преобразуют только определенное количество байтов в отличие от полных строк, которые в противном случае заканчивались бы в начальном состоянии сдвига).

Существует небольшая разница в том, из чего состоит внутреннее состояние в случае преобразования строк. Для mbstowcs целое число широких символов преобразуется за один вызов. Это связано с тем, что широкие символы имеют фиксированную ширину, а также потому, что параметр n вызова указывается в символах, а не в байтах. Напротив, для wcstombs параметр n указывается в байтах, а не в многобайтовых символах. Следовательно, состояние, сохраняемое для wcstombs, должно включать в себя не только состояние сдвига, но также и остаток частично выводимого многобайтового символа. Поскольку состояние, таким образом, состоит из нескольких частей, операции (загрузка и сохранение) над ним не будут атомарными в типичной архитектуре без дополнительной блокировки.

На этом этапе важно напомнить себе, что «безопасность потоков» имеет довольно техническое значение в POSIX, а именно то, что параллельные вызовы логически сериализуемы. Это не значит, что параллельное использование обязательно очень полезно. Поскольку все четыре функции поддерживают внутреннее состояние, трудно представить вызывающего, который обрабатывает одну линейную строку (одновременно) слева направо, но распределяет вызовы по нескольким потокам. Об этом свидетельствует введение mbrtowc, wcrtomb, mbsrtowcs и wcstombs в поправке 1 к ISO C 89/90, где флаг r стоит специально для слова «входящий».

Я не могу точно объяснить, почему наличие «атомарно доступного» внутреннего состояния должно упростить выполнение соответствующего вызова потокобезопасным способом (поскольку иногда требуется несколько обращений, загрузка и сохранение в течение одного вызов), но, возможно, это связано с тем, что бремя дополнительной блокировки (и перезагрузки) накладывается только на редко посещаемую ветвь кода, где происходит фактическое состояние сдвига.

Есть также еще один улов, чтобы объяснить. Параллельный поток может вызвать setlocale, изменяя категорию кодировки символов (LC_CTYPE) локали. Стандарт ISO C указывает, что такое действие приводит к тому, что текущее состояние (и, кстати, даже состояние, хорошо зафиксированное с помощью wcrtomb), становится неопределенным. Это связано с тем, что сдвиговые состояния разных локалей могут не отображаться друг на друга полезными или указанными способами. Несмотря на то, что это многопоточный сценарий, который может нарушить даже семейство функций «реентерабельного», он не обязательно создает препятствие для формально поточно-безопасной реализации, поскольку параметр локали можно кэшировать на протяжении каждого вызова.

1 голос
/ 28 января 2011

Я думаю, что это просто основано на предположении, что с набором данных, закодированных в широкие символы, программист может предсказать, сколько памяти необходимо выделить / освободить для потока из-за фиксированной ширины кодовой точки. Но при движении в другом направлении, в зависимости от кодировки, может быть не «предсказуемо», сколько памяти нужно будет выделить заранее, создавая тем самым больше места для ошибки.

Обновление: обнаружив более старую версию стандарта, я заметил, что в справочной странице для функции wctomb () есть разница в формулировке : «Функция wctomb () не требуется быть реентерабельным. Функция, которая не требует реентерабельности, не обязательно должна быть поточно-ориентированной. " Я думаю, что это предполагает другое неявное предположение, сделанное в стандарте: mbtowc () является или должен быть реентерабельным ...

1 голос
/ 28 января 2011

Обычный способ "включить Unicode" в программе на С - использовать wchar_t вместо char везде и вызывать широкоформатные версии функций стандартной библиотеки узких символов. Мне нравится этот подход, потому что сразу ясно, что переменная типа wchar_t* либо указывает на объект wchar_t, либо на широкую строку, но переменная типа char* может указывать на объект char, узкую строку в встроенная кодировка символов или многобайтовая строка в любой из десятков поддерживаемых кодировок символов. Имея так много принципиально разных значений char*, программист должен быть предельно осторожен, чтобы, например, не передавать многобайтовую строку в кодировке UTF-8 в функцию, которая ожидает узкую строку, или передавать многобайтовую строку в текущей кодировке к функции, которая ожидает строку в кодировке UTF-8. Возможно, идея обеспечения того, чтобы mbtowc и mbstowcs (функции, которые преобразуют многобайтовые строки в широкие строки) были поточно-ориентированными, а не функции, которые преобразуют из широких строк в многобайтовые строки, состоит в том, чтобы убедить программиста всегда хранить строковые данные в памяти программ в формате широких символов, где каждый символ представляет ровно один элемент набора символов выполнения, а не смесь узких строк и многобайтовых строк, использующих, возможно, различные кодировки символов. Возможно, стандартные авторы думали, что это более полезный подход или будет более распространенным.

Если вы подумаете о написании многопоточного серверного программного обеспечения с поддержкой Unicode на C, то следование шаблону хранения строковых данных в широком строковом формате помогает обеспечить разделение между строковыми данными, считываемыми «с провода», и строковыми данными в памяти программы. Всякий раз, когда приходит новое сообщение, содержащее полезную нагрузку строковых данных, подпрограмма C, которая анализирует сообщение и его строковую полезную нагрузку, может использовать функции ввода-вывода с узким символом и функции преобразования многобайтовых символов в широкие для считывания строки в память программы. Если несколько потоков анализируют входящие сообщения - как это обычно бывает), то крайне желательно, чтобы mbstowcs был безопасным для потоков.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...