【问题标题】:What is the purpose of the s==NULL case for mbrtowc?mbrtowc 的 s==NULL 案例的目的是什么?
【发布时间】:2011-06-10 05:14:08
【问题描述】:

mbrtowc 被指定为处理s(多字节字符指针)参数的NULL 指针,如下所示:

如果 s 是空指针,则 mbrtowc() 函数应等效于调用:

mbrtowc(NULL, "", 1, ps)

在这种情况下,参数 pwc 和 n 的值被忽略。

据我所知,这种用法在很大程度上是无用的。如果ps 没有存储任何部分转换的字符,则调用将简单地返回 0 而没有副作用。如果ps 存储了一个部分转换的字符,那么由于'\0' 作为多字节序列中的下一个字节无效('\0' 只能是字符串终止符),调用将返回(size_t)-1 和@987654330 @。并让ps 处于未定义状态。

预期用途似乎是重置状态变量,特别是当 NULLps 传递并且内部状态已被使用时,类似于 mbtowc 的有状态编码行为,但这是没有在任何地方指定据我所知,它与 mbrtowc 存储部分转换字符的语义冲突(如果 mbrtowc 在遇到 0 字节后重置状态一个潜在有效的初始子序列,它将无法检测到这个危险的无效序列)。

如果指定mbrtowc 仅在sNULL 时重置状态变量,而不是当它指向0 字节时,可能会出现理想的状态重置行为,但这种行为会违反书面标准。这是标准的缺陷吗?据我所知,一旦遇到非法序列,绝对没有办法重置内部状态(当psNULL 时使用),因此没有正确的程序可以使用mbrtowcps==NULL .

【问题讨论】:

  • 也许是委员会设计的案例?即:有人想确保为 pwcsps 中的每一个传递 NULL。

标签: c standards multibyte language-lawyer


【解决方案1】:

因为无论移位状态如何,'\0' 字节都必须转换为空宽字符(5.2.1.2 多字节字符),并且指定mbrtowc() 函数在转换为宽空字符时重置移位状态(7.24.6.3.2/3 mbrtowc 函数),调用mbrtowc( NULL, "", 1, ps) 将重置存储在ps 指向的mbstate_t 中的移位状态。如果调用mbrtowc( NULL, "", 1, NULL) 以使用库的内部mbstate_t 对象,它将被重置为初始状态。有关标准相关部分的引用,请参见答案的末尾。

我对 C 标准多字节转换函数并不是特别有经验(我对这种事情的经验是使用 Win32 API 进行转换)。

如果 mbrtowc() 处理一个被 0 字节缩短的“不完整字符”,它应该返回 (size_t)(-1) 以指示无效的多字节字符(从而检测您描述的危险情况)。在那种情况下,转换/转换状态是未指定的(我认为你基本上已经为那个字符串而烦恼了)。尝试转换但包含'\0' 的多字节“序列”无效,并且对后续数据始终有效。如果'\0' 不打算成为转换序列的一部分,那么它不应该包含在可用于处理的字节数中。

如果您可能会获得部分多字节字符的额外后续字节(例如来自网络流),则为部分多字节字符传递的 n 不应包含 0 字节,所以你会得到一个(size_t)(-2) 返回。在这种情况下,如果您在部分转换过程中传递了'\0',您将失去存在错误的事实,并且作为副作用重置使用中的mbstate_t 状态(无论是您自己的还是内部使用的是因为您为ps 传递了一个NULL 指针)。我想我基本上是在这里重申你的问题。

但是我认为可以检测和处理这种情况,但不幸的是它需要自己跟踪一些状态:

#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 函数

如果对应的宽字符是 空宽字符,结果 描述的状态是初始状态 转化状态

【讨论】:

  • 共识似乎是mbrtowc 确实存储部分转换字符的状态。这是基于标准中的文本“并且所有 n 个字节都已处理”。除了libutf8 的古老版本之外,所有已知的实现都以这种方式运行,libutf8 文档解释了为什么作者确信他的原始实现是错误的并对其进行了更改。而且这种行为与'\0'无条件重置状态不兼容...
  • 请注意,非初始“转换状态”和处于不完整字符中间是完全不同的两件事。我同意标准要求 '\0' 被解释为空宽字符,而不管移位状态如何,但即使它要求它在不完整字符之后被解释为空宽字符,也不会遵循这种行为,因为它将产生重大的安全问题并违反安全处理 UTF-8 的 Unicode 要求。
  • @R.:关于我关于重新开始部分转换的错误陈述,您完全正确。我已经更新了答案以消除我的错误(我希望如此)。我还对其进行了更新,以举例说明我认为如何检测和处理部分转换“正在进行”时正在处理的错误'\0' 字符。这不是我见过的最干净的东西,但也不是我写过的最丑陋的解决方法。如果我仍然遗漏了什么,请告诉我(显然,这不完全是我的专长——我正处于完全删除答案的边缘)。
  • 如果没有其他问题,您的 mbrtowc 函数的“解决方法”代码会在遇到 0 字节中间字符时重置状态,值得留在此处。你知道有什么必要的实现吗?
  • 我对标准的解读是,当mbrtowc() 传递一个'\0' 字节作为要处理的字节时,它需要重置状态。我发布的内容只是调用者在部分转换过程中检测到这一点的一种方式。我真的不知道实现的细节。我在多字节转换方面的经验是使用 Win32 API,而不是标准库,将字符串作为一个整体进行转换,因此我从不需要处理转换状态。对我来说,转换要么成功,要么失败 - 我不必尝试确定转换失败的位置或原因。
【解决方案2】:

在 5.2.1.2 多字节字符中,C 标准规定:

所有位为零的字节应被解释为与移位无关的空字符 状态。这样的字节不得作为任何其他多字节字符的一部分出现。

标准似乎区分了移位状态和转换状态,例如,7.24.6 提到:

所指向的对象所描述的转换状态会根据需要进行更改,以跟踪关联的多字节字符序列的移位状态和多字节字符内的位置

(强调由我添加)。但是,我认为其意图是将全零位的字节解释为空字符,而不管 mbstate_t 值如何编码整个转换状态,特别是“这样的字节不应作为任何其他多字节字符的一部分出现”意味着空字节不能出现在多字节字符中。如果空字节确实出现在多字节字符的第二个、第三个等字节应该在的错误输入中,那么我将标准解释为 EOF 处的部分多字节字符被静默忽略.

我对7.24.6.3.2的阅读,mbrtowc函数,对于sNULL的情况是这样的:下一个字节完成空宽字符,mbrtowc的返回值是0,结果状态是初始转换状态,因为:

如果对应的宽字符是空宽字符,则描述的结果状态是初始转换状态。

通过为sps 传递NULLmbrtowc 的内部 mbstate_t 被重置为初始状态。

【讨论】:

  • 再一次,这种解释取决于考虑适当的多字节字符由移位状态后跟字符字节组成。我从未见过对 UTF-8 这样做的实现。 (如果确实如此,那么mbtowc 将必须是有状态的并接受一次字节解码,而我从未见过允许这样做的实现。)在没有相反的证据的情况下,我倾向于相信标准允许 shift states 和长度大于一个字节的字符在 default shift state 中,只要它们不以基本字符集开头字节。
  • 您最后的评论毫无意义。 mbsrtowcs 对整个字符串进行操作,而 mbrtowc candoes 支持一次字节解码(返回 -2 但将部分字符存储在其mbstate_t 对象)。另一方面,mbtowc 的所有实现(注意缺少的r)我在部分 UTF-8 字符上使用了 return -1。我不知道这是如何与传统的多字节字符编码(Shift_JIS 等)一起使用的。
  • @R.:对不起。我没有想清楚。你是对的,当mbrtowc 返回 -2 时,它已经处理了每个 n 字节并适当地更新了 mbstate_t。此外,最终效果与 mbrtowc 被调用 n 次相同,每个字节调用一次(一次字节解码)。
  • @R.:mbtowc 在代码点的部分 UTF-8 编码上返回 -1 是正确的。当mbtowc 返回-1 时,调用者知道无法解码完整的宽字符,因此调用者需要检索更多数据并重新调用mbtowc,使用相同的s,但更高的n .
  • @R..: ...(n 应大于或等于 MB_CUR_MAX 否则 mbtowc 可能会再次返回 -1)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-24
  • 1970-01-01
  • 2016-07-12
  • 2017-02-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多