14

注意:我完全修改了这个问题,以更准确地反映我设置赏金的目的。请原谅与这可能已经创建的已经给出的答案的任何不一致之处。我不想创建一个新问题,因为以前对此问题的答案可能会有所帮助。


我正在实现一个 C 标准库,并且对标准的一个特定角落感到困惑。

该标准根据、和的定义定义了scanf函数族(%d、%i、%u、%o、%x)接受的数字格式。strtolstrtoulstrtod

该标准还规定,fscanf()最多只能将一个字符放回输入流中,因此某些序列被 接受strtolstrtoul并且strtod是不可接受的fscanf(ISO/IEC 9899:1999,脚注 251)。

我试图找到一些会表现出这种差异的值。事实证明,十六进制前缀“0x”,后跟一个不是十六进制数字的字符,是两个函数系列不同的一种情况。

有趣的是,显然没有两个可用的 C 库似乎在输出上达成一致。(请参阅此问题末尾的测试程序和示例输出。)

我想听到的是在解析“0xz”时会被视为符合标准的行为吗?. 理想情况下,引用标准中的相关部分来说明这一点。

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main()
{
    int i, count, rc;
    unsigned u;
    char * endptr = NULL;
    char culprit[] = "0xz";

    /* File I/O to assert fscanf == sscanf */
    FILE * fh = fopen( "testfile", "w+" );
    fprintf( fh, "%s", culprit );
    rewind( fh );

    /* fscanf base 16 */
    u = -1; count = -1;
    rc = fscanf( fh, "%x%n", &u, &count );
    printf( "fscanf:  Returned %d, result %2d, consumed %d\n", rc, u, count );
    rewind( fh );

    /* strtoul base 16 */
    u = strtoul( culprit, &endptr, 16 );
    printf( "strtoul:             result %2d, consumed %d\n", u, endptr - culprit );

    puts( "" );

    /* fscanf base 0 */
    i = -1; count = -1;
    rc = fscanf( fh, "%i%n", &i, &count );
    printf( "fscanf:  Returned %d, result %2d, consumed %d\n", rc, i, count );
    rewind( fh );

    /* strtol base 0 */
    i = strtol( culprit, &endptr, 0 );
    printf( "strtoul:             result %2d, consumed %d\n", i, endptr - culprit );

    fclose( fh );
    return 0;
}

/* newlib 1.14

fscanf:  Returned 1, result  0, consumed 1
strtoul:             result  0, consumed 0

fscanf:  Returned 1, result  0, consumed 1
strtoul:             result  0, consumed 0
*/

/* glibc-2.8

fscanf:  Returned 1, result  0, consumed 2
strtoul:             result  0, consumed 1

fscanf:  Returned 1, result  0, consumed 2
strtoul:             result  0, consumed 1
*/

/* Microsoft MSVC

fscanf:  Returned 0, result -1, consumed -1
strtoul:             result  0, consumed 0

fscanf:  Returned 0, result  0, consumed -1
strtoul:             result  0, consumed 0
*/

/* IBM AIX

fscanf:  Returned 0, result -1, consumed -1
strtoul:             result  0, consumed 1

fscanf:  Returned 0, result  0, consumed -1
strtoul:             result  0, consumed 1
*/
4

8 回答 8

8

在 comp.std.c 上与 PL22.11 (ANSI "C") 的 Vice-char Fred J. Tydeman 的交流阐明了这一点:

fscanf

输入项被定义为输入字符的最长序列[...],它是匹配输入序列或匹配输入序列的前缀。(7.19.6.2 P9)

这使得“0x”成为匹配输入序列的前缀的最长序列。(即使进行%i转换,因为十六进制“0x”比十进制“0”的序列更长。)

输入项之后的第一个字符(如果有)保持未读状态。(7.19.6.2 P9)

这使得fscanf读取“z”,并将其作为不匹配放回(遵守脚注 251 的单字符后推限制))。

如果输入项不是匹配序列,则指令执行失败:此条件为匹配失败。(7.19.6.2 P10)

这使得“0x”无法匹配,即fscanf不应该分配任何值,返回零(如果%xor%i是第一个转换说明符),并将“z”作为输入流中的第一个未读字符。

strtol

strtol( 和)的定义strtoul在一个关键点上有所不同:

主题序列被定义为输入字符串的最长初始子序列,从第一个非空白字符开始,即预期形式。(7.20.1.4 P4,强调我的)

这意味着strtol应该寻找最长的有效序列,在这种情况下是“0”。它应该指向endptr“x”,并返回零作为结果。

于 2009-09-19T05:34:20.237 回答
2

我不相信允许解析产生不同的结果。Plauger 参考只是指出strtol()实现可能是一个不同的、更有效的版本,因为它可以完全访问整个字符串。

于 2009-09-15T09:12:45.390 回答
2

根据 C99 规范,scanf()函数族解析整数的方式与strto*()函数族相同。例如,对于转换说明符,x它的内容如下:

匹配可选带符号的十六进制整数,其格式与函数主题序列的预期格式相同,参数strtoul值为 16 base

因此,如果sscanf()strtoul()给出不同的结果,则 libc 实现不符合要求。

但是,您的示例代码的预期结果应该是有点不清楚:

strtoul()接受0xor 0Xif baseis的可选前缀16,并且规范读取

主题序列被定义为输入字符串的最长初始子序列,从第一个非空白字符开始,即具有预期形式。

对于字符串"0xz",我认为预期形式的最长初始子序列是"0",所以值应该是0endptr参数应该设置为x

mingw-gcc 4.4.0 不同意并且无法同时使用strtoul()和解析字符串sscanf()。原因可能是预期形式的最长初始子序列是"0x"- 这不是有效的整数文字,因此不进行解析。

我认为对标准的这种解释是错误的:预期形式的子序列应该始终产生一个有效的整数值(如果超出范围,则MIN/MAX值被返回并errno设置为ERANGE)。

cygwin-gcc 3.4.4(据我所知使用 newlib)也不会解析文字strtoul(),但会根据我对标准的解释来解析字符串sscanf()

请注意,我对标准的解释很容易出现您的初始问题,即标准只保证能够执行ungetc()一次。要确定 the0x是否是文字的一部分,您必须提前阅读两个字符:thex和后面的字符。如果不是十六进制字符,则必须将其推回。如果有更多要解析的标记,您可以缓冲它们并解决此问题,但如果它是最后一个标记,则必须同时处理ungetc()这两个字符。

我不确定如果失败该怎么fscanf()办。ungetc()也许只是设置流的错误指示器?

于 2009-09-15T14:53:30.343 回答
2

总结一下解析数字时按照标准应该发生的事情:

  • 如果fscanf()成功,结果必须与通过strto*()
  • 相反strto*()fscanf()如果

    输入字符的最长序列[...],它是匹配输入序列的前缀,或者是匹配输入序列的前缀

    根据定义fscanf()不是

    具有预期形式的最长初始子序列 [...]

    根据定义strto*()

这有点难看,但fscanf()应该是贪婪的要求的必然结果,但不能推回一个以上的字符。

一些库实现者选择了不同的行为。在我看来

  • strto*()结果不一致是愚蠢的(bad mingw
  • 推回一个以上的字符,因此fscanf()接受所有接受的值strto*()违反了标准,但这是合理的(如果他们没有搞砸的话,为 newlib 欢呼strto*():(
  • not pushing back the non-matching characters but still only parsing the ones of 'expected form' seems dubious as characters vanish into thin air (bad glibc)
于 2009-09-19T07:55:26.003 回答
0

我不确定我是否理解这个问题,但一方面 scanf() 应该处理 EOF。scanf() 和 strtol() 是不同种类的野兽。也许您应该比较 strtol() 和 sscanf() ?

于 2009-09-15T07:56:22.143 回答
0

重写问题后答案已过时。不过评论中有一些有趣的链接。


如有疑问,请编写测试。——谚语

在测试了我能想到的转换说明符和输入变化的所有组合之后,我可以说这两个函数系列没有给出相同的结果是正确的。(至少在 glibc 中,这是我可用于测试的。)

当三种情况同时出现时,差异就会出现:

  1. 您使用"%i""%x"(允许十六进制输入)。
  2. 输入包含(可选)"0x"十六进制前缀。
  3. 十六进制前缀后面没有有效的十六进制数字。

示例代码:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    char * string = "0xz";
    unsigned u;
    int count;
    char c;
    char * endptr;

    sscanf( string, "%x%n%c", &i, &count, &c );
    printf( "Value: %d - Consumed: %d - Next char: %c - (sscanf())\n", u, count, c );
    i = strtoul( string, &endptr, 16 );
    printf( "Value: %d - Consumed: %td - Next char: %c - (strtoul())\n", u, ( endptr - string ), *endptr );
    return 0;
}

输出:

Value: 0 - Consumed: 1 - Next char: x - (sscanf())
Value: 0 - Consumed: 0 - Next char: 0 - (strtoul())

这让我很困惑。显然sscanf()不会在'x', 或者它无法解析任何 "0x"前缀的十六进制。所以它已经阅读'z'并发现它不匹配。但它决定只使用前导"0"作为值。这将意味着推动'z' 'x'背部。(是的,我知道sscanf(),我在这里使用它是为了方便测试,它不会在流上运行,但我强烈认为它们使所有...scanf()函数的行为都相同以保持一致性。)

所以... one-charungetc()并不是真正的原因,在这里... ?:-/

是的,结果不同。不过,我仍然无法正确解释它...... :-(

于 2009-09-15T13:23:36.033 回答
0

我不确定实现 scanf() 可能与 ungetc() 有什么关系。scanf() 可以用完流缓冲区中的所有字节。ungetc() 只是将一个字节推到缓冲区的末尾,并且偏移量也发生了变化。

scanf("%d", &x);
ungetc('9', stdin);
scanf("%d", &y);
printf("%d, %d\n", x, y);

如果输入为“100”,则输出为“100, 9”。我看不出 scanf() 和 ungetc() 如何相互干扰。对不起,如果我添加了一个幼稚的评论。

于 2009-09-15T19:24:13.383 回答
0

对于scanf()函数以及strtol()函数的输入,请参见第 2节。7.20.1.4 P7表示:如果主题序列为空或不具有预期形式,则不进行转换;nptr 的值存储在 endptr 指向的对象中,前提是 endptr 不是空指针。此外,您必须考虑解析那些在Sec 规则下定义的令牌的规则。6.4.4 常量,在Sec中指出的规则。7.20.1.4 P5

其余的行为,例如errno值,应该是特定于实现的。例如,在我的 FreeBSD 机器上,我得到了EINVALERANGE值,在 Linux 下也会发生同样的情况,其中标准引用仅指向ERANGE errno 值。

于 2009-09-18T20:12:08.173 回答