Фундаментальная асимметрия заключается в том, что 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
), становится неопределенным. Это связано с тем, что сдвиговые состояния разных локалей могут не отображаться друг на друга полезными или указанными способами. Несмотря на то, что это многопоточный сценарий, который может нарушить даже семейство функций «реентерабельного», он не обязательно создает препятствие для формально поточно-безопасной реализации, поскольку параметр локали можно кэшировать на протяжении каждого вызова.