Какова цель случая s==NULL для mbrtowc?

mbrtowc указан для обработки указателя NULL для аргумента s (многобайтовый символьный указатель) следующим образом:

Если s является нулевым указателем, функция mbrtowc() должна быть эквивалентна вызову:

mbrtowc(NULL, "", 1, ps)

В этом случае значения аргументов pwc и n игнорируются.

Насколько я могу судить, это использование в значительной степени бесполезно. Если ps не хранит частично преобразованный символ, вызов просто вернет 0 без каких-либо побочных эффектов. Если ps хранит частично преобразованный символ, то, поскольку '\0' недопустим в качестве следующего байта в многобайтовой последовательности ('\0' может быть только знаком конца строки), вызов вернет (size_t)-1 с errno==EILSEQ. и оставить ps в неопределенном состоянии.

Предполагаемое использование, по-видимому, состояло в том, чтобы сбросить переменную состояния, особенно когда NULL передается вместо ps и используется внутреннее состояние, аналогично поведению mbtowc с кодировками с отслеживанием состояния, но это нигде не указано как насколько я могу судить, и это противоречит семантике хранения частично преобразованных символов mbrtowc (если бы mbrtowc сбрасывал состояние при встрече с 0 байтом после потенциально допустимой начальной подпоследовательности, он не смог бы обнаружить эту опасную недопустимую последовательность ).

Если бы mbrtowc было указано для сброса переменной состояния только тогда, когда s равно NULL, но не когда оно указывает на 0 байт, желательное поведение сброса состояния было бы возможно, но такое поведение нарушило бы написанный стандарт. Является ли это дефектом стандарта? Насколько я могу судить, нет абсолютно никакого способа сбросить внутреннее состояние (используемое, когда ps равно NULL) после того, как была обнаружена недопустимая последовательность, и, таким образом, ни одна правильная программа не может использовать mbrtowc с ps==NULL.


person R.. GitHub STOP HELPING ICE    schedule 17.01.2011    source источник
comment
Может быть, дело в дизайн-комитете? то есть: кто-то хотел убедиться, что передача NULL для каждого из pwc, s и ps была указана.   -  person ninjalj    schedule 17.01.2011


Ответы (2)


Поскольку байт '\0' должен преобразовываться в широкий нулевой символ независимо от состояния сдвига (5.2.1.2 Многобайтовые символы), а функция mbrtowc() предназначена для сброса состояния сдвига при преобразовании в широкий нулевой символ (7.24.6.3. 2/3 Функция mbrtowc), вызов mbrtowc( NULL, "", 1, ps) сбросит состояние смены, хранящееся в mbstate_t, на которое указывает ps. И если mbrtowc( NULL, "", 1, NULL) вызывается для использования внутреннего объекта библиотеки mbstate_t, он будет сброшен в начальное состояние. См. Конец ответа для ссылок на соответствующие биты стандарта.

Я ни в коем случае не особенно разбираюсь в стандартных функциях многобайтового преобразования C (мой опыт работы с такими вещами заключался в использовании API-интерфейсов Win32 для преобразования).

Если mbrtowc() обрабатывает «неполный символ», который сокращен на 0 байт, он должен вернуть (size_t)(-1), чтобы указать недопустимый многобайтовый символ (и, таким образом, обнаружить опасную ситуацию, которую вы описываете). В этом случае состояние преобразования/сдвига не указано (и я думаю, что вы в основном застряли в этой строке). Многобайтовая «последовательность», в которой была предпринята попытка преобразования, но содержащая '\0', недействительна и всегда будет действительна для последующих данных. Если '\0' не должен был быть частью преобразованной последовательности, то он не должен был быть включен в подсчет байтов, доступных для обработки.

Если вы находитесь в ситуации, когда вы можете получить дополнительные последующие байты для частичного многобайтового символа (скажем, из сетевого потока), n, которое вы передали для частичного многобайтового символа, не должно включать 0 байт, поэтому вы получите (size_t)(-2) вернулся. В этом случае, если вы передадите '\0' в середине частичного преобразования, вы потеряете тот факт, что произошла ошибка, и в качестве побочного эффекта сбросите используемое состояние mbstate_t (будь то ваше собственное или внутреннее состояние). используется, потому что вы передали указатель NULL для ps). Я думаю, что я, по сути, повторяю ваш вопрос здесь.

Однако я думаю, что эту ситуацию можно обнаружить и обработать, но, к сожалению, это требует самостоятельного отслеживания некоторого состояния:

#define MB_ERROR    ((size_t)(-1))
#define MB_PARTIAL  ((size_t)(-2))

// function to get a stream of multibyte characters from somewhere
int get_next(void);

int bar(void)
{
    char c;
    wchar_t wc;
    mbstate_t state = {0};

    int in_partial_convert = 0;

    while ((c = get_next()) != EOF)
    {
        size_t result = mbrtowc( &wc, &c, 1, &state);

        switch (result) {
        case MB_ERROR:
            // this multibyte char is invalid
            return -1;
        case MB_PARTIAL:
            // do nothing yet, we need more data
            // but remember that we're in this state
            in_partial_convert = 1;
            break;
        case 1:
            // output the competed wide char
            in_partial_convert = 0;     // no longer in the middle of a conversion
            putwchar(wc);
            break;
        case 0:
            if (in_partial_convert) {
                // this 'last' multibyte char was mal-formed
                // return an error condidtion
                return -1;
            }
            // end of the multibyte string
            // we'll handle similar to EOF
            return 0;
        }
    }

    return 0;
}

Возможно, это не идеальная ситуация, но я думаю, что это показывает, что она не полностью сломана, чтобы ее невозможно было использовать.


Ссылки на стандарты:

5.2.1.2 Многобайтовые символы

  • Многобайтовый набор символов может иметь кодировку, зависящую от состояния, при этом каждая последовательность многобайтовых символов начинается с начального состояния сдвига и входит в другие состояния сдвига, зависящие от локали, когда в последовательности встречаются определенные многобайтовые символы. В начальном состоянии сдвига все однобайтовые символы сохраняют свою обычную интерпретацию и не изменяют состояние сдвига. Интерпретация последующих байтов в последовательности зависит от текущего состояния сдвига.

  • Байт со всеми нулевыми битами должен интерпретироваться как нулевой символ независимо от состояния сдвига.

  • Байт со всеми нулевыми битами не должен встречаться во втором или последующих байтах многобайтового символа.

7.24.6.3.2/3 Функция mbrtowc

Если соответствующий расширенный символ является пустым широким символом, описываемое результирующее состояние является начальным состоянием преобразования.

person Michael Burr    schedule 17.01.2011
comment
Похоже, консенсус заключается в том, что mbrtowc действительно хранит состояние для частично преобразованных символов. Это основано на тексте, и все n байтов были обработаны в стандарте. Все известные реализации ведут себя так, кроме древних версий libutf8, а в документации libutf8 есть объяснение, почему автор был убежден, что его первоначальная реализация была неправильной, и изменил ее. И такое поведение несовместимо с '\0' безусловным сбросом состояния... - person R.. GitHub STOP HELPING ICE; 17.01.2011
comment
Обратите внимание, что неначальное состояние сдвига и нахождение в середине незавершенного персонажа — две совершенно разные вещи. Я согласен с тем, что стандарт требует, чтобы '\0' интерпретировался как широкий нулевой символ независимо от состояния сдвига, но даже если бы он требовал, чтобы он интерпретировался как широкий нулевой символ после неполного символа, никакая разумная реализация не будет следовать такому поведению, поскольку это будет создают серьезные проблемы с безопасностью и нарушают требования Unicode для безопасной обработки UTF-8. - person R.. GitHub STOP HELPING ICE; 17.01.2011
comment
@R .: вы совершенно правы насчет моих искажений о перезапуске частичного преобразования. Я обновил ответ, чтобы удалить свои ошибки (надеюсь). Я также обновил его, чтобы показать пример того, как, по моему мнению, ошибочный символ '\0', обрабатываемый во время частичного преобразования, может быть обнаружен и обработан. Это не самая чистая вещь, которую я видел, но и далеко не самый уродливый обходной путь, который я написал. Дайте мне знать, если я все еще что-то упускаю (очевидно, это не совсем мой опыт - я почти полностью удаляю ответ). - person Michael Burr; 17.01.2011
comment
По крайней мере, ваш обходной код для функции mbrtowc, которая сбрасывает состояние при обнаружении 0-байтового среднего символа, стоит оставить здесь. Знаете ли вы какие-либо реализации, где это необходимо? - person R.. GitHub STOP HELPING ICE; 17.01.2011
comment
Мое прочтение стандарта заключается в том, что когда mbrtowc() передается '\0' байта в качестве байта для обработки, ему необходимо сбросить состояние. То, что я разместил, было бы просто способом для вызывающего абонента обнаружить это в середине частичного преобразования. Я действительно не знаю о деталях реализации. Мой опыт многобайтового преобразования заключается в использовании API-интерфейсов Win32, а не стандартной библиотеки, для преобразования строки в целом, поэтому мне никогда не приходилось иметь дело с состоянием преобразования. И для меня преобразование либо работало, либо нет - мне не нужно было пытаться определить, где и почему преобразование не удалось. - person Michael Burr; 17.01.2011
comment
@Michael: Самое близкое, что я могу найти в стандарте, - это требование, чтобы 0 байт интерпретировался как нулевой широкий символ независимо от состояния сдвига. Это применимо только в том случае, если начальные байты многобайтового символа считаются по стандарту составляющими состояние сдвига. Но рассмотрение частичных символов как состояний сдвига, кажется, противоречит семантике mbtowc или, по крайней мере, обычным реализациям, которые не обрабатывают частичные символы. - person R.. GitHub STOP HELPING ICE; 24.01.2011
comment
@R.: Это применимо только в том случае, если начальные байты многобайтового символа считаются по стандарту составляющими «состояние сдвига». Они есть. - person Daniel Trebbien; 26.01.2011
comment
@Daniel: Где ты это взял? - person R.. GitHub STOP HELPING ICE; 26.01.2011
comment
@R.: Я понял это из 5.2.1.2: многобайтовый набор символов может иметь кодировку, зависящую от состояния, при этом каждая последовательность многобайтовых символов начинается с начального состояния сдвига и переходит в другие состояния сдвига, зависящие от локали, когда встречаются определенные многобайтовые символы. в последовательности. В начальном состоянии сдвига все однобайтовые символы сохраняют свою обычную интерпретацию и не изменяют состояние сдвига. Интерпретация последующих байтов в последовательности зависит от текущего состояния сдвига. - person Daniel Trebbien; 26.01.2011
comment
@Р. - Я добавил абзац в начало текста ответа, который, как мне кажется, показывает, почему объект состояния сдвига (внутренний или предоставленный вызывающей стороной) должен быть сброшен в исходное состояние, когда mbrtowc() вызывается с исходной строкой, указывающей на '\0' байт. Соответствующие биты из стандарта цитируются в конце (сноски, если хотите). - person Michael Burr; 26.01.2011
comment
В цитате из 5.2.1.2 я рассматриваю третий пункт как указание на то, что обнаружение нулевого байта после частичного символа должно интерпретироваться как недопустимая последовательность (с результирующим неопределенным состоянием для объекта mbstate_t). Пуля 2 касается только состояний сдвига, а не частичных символов. - person R.. GitHub STOP HELPING ICE; 26.01.2011
comment
@Р. - Я прочитал маркеры в 5.2.1.2 как означающие, что байт '\0', встречающийся в многобайтовом потоке, может однозначно считаться нулевым символом. Описанное поведение mbrtowc() при передаче пустой исходной строки, похоже, определенно предназначено для сброса объекта mbstate_t независимо от текущего состояния, и я думаю, что 5.2.1.2 дает ему возможность делать это четко определенным образом. Опять же, это мое прочтение — я определенно не состою в комитете по стандартам, и, по общему признанию, у меня нет большого опыта работы с этими стандартными функциями. - person Michael Burr; 26.01.2011

В 5.2.1.2, Многобайтовые символы, стандарт C гласит:

Байт со всеми нулевыми битами должен интерпретироваться как нулевой символ независимо от состояния сдвига. Такой байт не должен встречаться как часть любого другого многобайтового символа.

Стандарт, по-видимому, проводит различие между состоянием сдвига и состоянием преобразования, как, например, в 7.24.6 упоминается:

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

(выделено мною). Однако я думаю, что цель состоит в том, чтобы интерпретировать байт со всеми нулевыми битами как нулевой символ независимо от значения mbstate_t, которое кодирует все состояние преобразования, в частности, как «такой байт не должен встречаться как часть любого другого многобайтового символа» подразумевает, что нулевой байт не может встречаться внутри многобайтового символа. Если нулевой байт действительно появляется в ошибочном вводе, где должен быть второй, третий и т. д. байт многобайтового символа, то я интерпретирую Стандарт как говорящий, что неполный многобайтовый символ в EOF молча игнорируется .

Мое прочтение 7.24.6.3.2, функция mbrtowc для случая, когда s равно NULL, таково: следующий 1 байт завершает нулевой широкий символ, возвращаемое значение mbrtowc равно 0, а результирующее состояние является начальным состоянием преобразования. потому что:

Если соответствующий расширенный символ является пустым широким символом, описываемое результирующее состояние является начальным состоянием преобразования.

Передавая NULL как для s, так и для ps, внутреннее mbstate_t для mbrtowc сбрасывается в начальное состояние.

person Daniel Trebbien    schedule 25.01.2011
comment
Опять же, эта интерпретация зависит от рассмотрения правильных многобайтовых символов, состоящих из состояния сдвига, за которым следует символьный байт. Я никогда не видел реализации, которая делает это для UTF-8. (Если бы это было так, то mbtowc должен был бы сохранять состояние и принимать побайтовое декодирование, а я никогда не видел реализации, позволяющей такое.) В отсутствие доказательств обратного я склонен считают, что стандарт допускает как состояния сдвига, так и символы длиной более одного байта в состоянии сдвига по умолчанию, если они не начинаются с байта базового набора символов. - person R.. GitHub STOP HELPING ICE; 26.01.2011
comment
Ваш последний комментарий не имеет смысла. mbsrtowcs работает с целыми строками, тогда как mbrtowc может и поддерживает побайтовое декодирование (возвращает -2, но сохраняет частичный символ, хранящийся в его объекте mbstate_t). С другой стороны, во всех реализациях mbtowc (обратите внимание на отсутствие r) я использовал return -1 для неполных символов UTF-8. Я понятия не имею, как это предназначено для работы с устаревшими многобайтовыми кодировками символов (Shift_JIS и т. д.). - person R.. GitHub STOP HELPING ICE; 26.01.2011
comment
@Р.: Извините. Я не думал ясно. Вы правы в том, что когда mbrtowc возвращает -2, он обработал каждый из n байтов и соответствующим образом обновил mbstate_t. Кроме того, конечный эффект такой же, как если бы mbrtowc вызывалась n раз, по одному разу для каждого байта (побайтовое декодирование). - person Daniel Trebbien; 26.01.2011
comment
@R.: То, что mbtowc возвращает -1 в частичной кодировке UTF-8 кодовой точки, верно. Когда mbtowc возвращает -1, вызывающая сторона знает, что полный широкий символ не может быть декодирован, поэтому вызывающей стороне необходимо получить дополнительные данные и повторно вызвать mbtowc с тем же s, но более высоким n. - person Daniel Trebbien; 26.01.2011
comment
@R..: ... (n должно быть больше или равно MB_CUR_MAX, иначе mbtowc может снова вернуть -1). - person Daniel Trebbien; 26.01.2011
comment
@R..: Я не понимаю твой первый комментарий. Под многобайтовым символом Стандарт подразумевает последовательность из одного или нескольких байтов, представляющую член расширенного набора символов либо источника, либо среды выполнения. Например, при использовании UTF-8 многобайтовые символы имеют длину от 1 до 4 байтов. - person Daniel Trebbien; 26.01.2011
comment
Под правильным многобайтовым символом я просто подразумеваю многобайтовый символ, состоящий из более чем одного байта. Я не вижу в стандарте языка, который приравнивал бы неполные символы (например, байт c3 в UTF-8) к состояниям сдвига (которые я и, по-видимому, многие другие люди подразумевают байты, такие как 0e и 0f в ISO-2022). - person R.. GitHub STOP HELPING ICE; 26.01.2011
comment
@R..: На самом деле, я должен быть более точным и сказать октет. UTF-8 кодирует кодовые точки от 1 до 4 октетов. - person Daniel Trebbien; 26.01.2011
comment
Обратите внимание, что 7.24.1 определяет mbstate_t как тип объекта, отличный от типа массива, который может содержать информацию о состоянии преобразования, необходимую для преобразования между последовательностями многобайтовых символов и расширенными символами. Слово «состояние сдвига» не используется, что заставляет меня думать, что информация о состоянии преобразования потенциально включает в себя что-то иное, чем состояние сдвига, которое может быть только частично декодированной начальной подпоследовательностью многобайтового символа, который не рассматривается реализацией как состояние сдвига. - person R.. GitHub STOP HELPING ICE; 26.01.2011
comment
@R..: Под состоянием сдвига Стандарт подразумевает информацию о состоянии преобразования. В 5.2.1.2, который я также процитировал в комментарии к ответу Майкла, в стандарте есть многобайтовый набор символов, который может иметь кодировку, зависящую от состояния, в которой каждая последовательность многобайтовых символов начинается с начальное состояние сдвига и переходит в другие состояния сдвига, зависящие от локали, когда в последовательности встречаются определенные многобайтовые символы. Таким образом, хотя в определении mbstate_t не упоминается состояние сдвига, я читаю 5.2.1.2 как неявно определяющее состояние сдвига как означающее всю информацию о состоянии преобразования. - person Daniel Trebbien; 26.01.2011
comment
@R..: Это действительно сбивает с толку, потому что состояние сдвига также означает что-то другое. Например, некоторые люди пишут, что UTF-8 не использует состояние сдвига, что является правильным утверждением в соответствии с одним значением состояния сдвига, но не в соответствии с определением стандарта C, я думаю. - person Daniel Trebbien; 26.01.2011
comment
@R..: По сути, мой аргумент заключается в том, что состояние сдвига в точности означает состояние преобразования, потому что, когда в стандарте говорится о кодировках, зависящих от состояния, в нем упоминается только состояние сдвига. - person Daniel Trebbien; 26.01.2011
comment
Ваши рассуждения цикличны. Только кодировки, зависящие от состояния, имеют состояния сдвига; это почти тавтология. Но UTF-8 явно не зависит от состояния как в смысле обычного языка, так и в смысле mbtowc (без r). mbrtowc был добавлен в язык после mbtowc, и мне кажется довольно странным, что добавление нового интерфейса изменит состояние (или его отсутствие), которое mbtowc сообщает для UTF-8. - person R.. GitHub STOP HELPING ICE; 26.01.2011
comment
@R..: Если вы декодируете многобайтовую строку в кодировке UTF-8 в широкую строку, и при первом вызове mbrtowc n равно 1, а s равно "\xC2\x..., то декодирование второго байта не в исходное состояние преобразования. Это для меня зависит от состояния. mbtowc не принимает объект mbstate_t, который кодирует информацию о состоянии сдвига или состоянии преобразования, потому что это все или ничего; либо mbtowc может декодировать многобайтовый символ в широкий символ, либо нет. Если это невозможно, то mbtowc возвращает -1, и вызывающая сторона знает, что нужно повторить попытку с более высоким n. - person Daniel Trebbien; 26.01.2011
comment
Фраза кодировка, зависящая от состояния, используется в спецификации mbtowc: mbtowc(0,0,0) возвращает 0 тогда и только тогда, когда кодировки символов не имеют кодировки, зависящей от состояния. Я не вижу смысла в том, чтобы эта функция возвращала 1 для UTF-8, особенно если реализация была написана до того, как в язык было добавлено mbrtowc. Что касается mbrtowc, то он имеет состояние декодирования, но это не означает, что кодирование символов зависит от состояния. - person R.. GitHub STOP HELPING ICE; 26.01.2011
comment
@R ..: Я убежден, что вы правы в отношении «информации о состоянии преобразования», потенциально [включая] что-то иное, чем «состояние сдвига». Я прочитал все упоминания о зависимости от состояния в Стандарте, а также прочитал в 7.24.6, что состояние преобразования, описываемое объектом, на который указывает указатель, изменяется по мере необходимости для отслеживания состояния сдвига и положения внутри многобайтового символа. для связанной многобайтовой последовательности символов. Конечно, у меня есть доступ только к окончательному проекту ISO/IEC 9899:TC2, но я думаю, что эта формулировка не была изменена в опубликованном стандарте. - person Daniel Trebbien; 26.01.2011
comment
@R..: Кажется, что для решения этой проблемы в стандарте должно быть указано, что байт со всеми нулевыми битами должен интерпретироваться как нулевой символ, не зависящий от состояния преобразования. Однако я считаю, что целью передачи NULL вместо s является сброс внутреннего состояния (или передача mbstate_t, если ps не является NULL). - person Daniel Trebbien; 26.01.2011
comment
Молчаливое игнорирование неполного многобайтового символа, оканчивающегося нулевым байтом, было бы катастрофическим ящиком Пандоры с уязвимостями. Стандарт однозначно указывает, что если mbrtowc увидит их вместе (например, c3 00 с длиной ›=2), это недопустимый символ, и mbrtowc должен вернуть -1. Я согласен, что не совсем понятно, что должно произойти в случае двух отдельных вызовов с передачей одного байта каждый раз, но я не вижу языка, который требует вашей интерпретации, и это явно вредит безопасности. - person R.. GitHub STOP HELPING ICE; 26.01.2011
comment
Кстати, я не уверен, почему вы хотите решить проблему, сделав интерфейс небезопасным (не обнаруживая опасных ошибочных данных) и/или более сложным в использовании. В чем проблема? Что не может быть никакого способа сбросить внутреннее состояние, когда оно переходит в недопустимое состояние? Для этого есть два простых решения: либо (1) отказаться от использования внутреннего состояния, либо (2) указать, что если s является указателем NULL, внутренний объект mbstate_t сбрасывается (вместо указания случая указателя NULL в терминах другого вызов, который не обязательно имеет такое поведение). - person R.. GitHub STOP HELPING ICE; 26.01.2011
comment
@R..: Не могли бы вы пояснить, почему молчаливое игнорирование частичного многобайтового символа в конце небезопасно? Кроме того, какой текст из стандарта говорит о том, что нулевой байт, завершающий неполный многобайтовый символ, следует рассматривать как недопустимый многобайтовый символ? - person Daniel Trebbien; 27.01.2011
comment
Две неидентичные строки байтов никогда не должны преобразовываться в одну и ту же строку расширенных символов. Это фундаментальное свойство UTF-8. Представьте, что что-то вроде "..\xc2" декодируется в L"..". Или даже просто строка, которая была подтверждена (как строка байтов) как непустая, позже декодированная в пустую широкую строку. Такое отвратительное поведение возможно с некоторыми уродливыми устаревшими многобайтовыми кодировками (например, на основе ISO-2022), которые обычно не поддерживаются в системах Unix, поскольку приводят к проблемам с безопасностью. - person R.. GitHub STOP HELPING ICE; 27.01.2011
comment
Что касается текста из стандарта..., согласно пункту 3 пункта 5.2.1.2, это не может быть допустимым символом. Если бы префикс рассматривался как состояние сдвига, а не как частичный символ, то его можно было бы отбросить как избыточный сдвиг перед завершающим нулем, но я специально указал частичный многобайтовый символ, а не префикс сдвига. Для поддержки UTF-8, которая соответствует Unicode, вы не можете рассматривать префиксы как состояния сдвига, потому что обнаружение неполной или недопустимой последовательности должно быть сообщено как ошибка (предпочтительно) или через символ замены. - person R.. GitHub STOP HELPING ICE; 27.01.2011
comment
Обратите внимание, что подход с заменой символов, который допускает Unicode, невозможен с mbrtowc API в этой ситуации. Это связано с тем, что при встрече с c3 00 придется интерпретировать c3 как 1-байтовый многобайтовый символ, что приведет к замене символа, поскольку он не может (по стандарту C) рассматривать 00 как часть многобайтового символа. Но тогда декодирование любого символа UTF-8, начинающегося с c3, будет невозможно. - person R.. GitHub STOP HELPING ICE; 27.01.2011