解析数字时scanf()和strtol()/ strtod()之间的区别

时间:2009-09-15 07:44:04

标签: c standards standards-compliance

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


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

该标准根据scanf的定义{{1}定义strtol函数系列(%d,%i,%u,%o,%x)接受的数字格式}和strtoul

标准还说strtod只会在输入流中放回最多一个字符,因此fscanf()strtol和{{1}接受一些序列strtoul是不可接受的(ISO / IEC 9899:1999,脚注251)。

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

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

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

strtod

8 个答案:

答案 0 :(得分:6)

与comp.std.c的PL22.11(ANSI“C”)副主席Fred J. Tydeman的沟通对此有所了解:

<强> fscanf

  

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

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

  

第一个字符,如果有的话,在。之后   输入项目仍未读取。 (7.19.6.2 P9)

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

  

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

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

<强> strtol

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

  

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

这意味着strtol应该查找最长的有效序列,在本例中为“0”。它应该将endptr指向“x”,并返回零作为结果。

答案 1 :(得分:3)

我不相信解析会产生不同的结果。 Plaugher引用只是指出strtol()实现可能是一个不同的,更高效的版本,因为它可以完全访问整个字符串。

答案 2 :(得分:3)

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

  

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

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

sample code的预期结果应该是什么有点不清楚,但是:

如果strtoul()0x,则

0X接受base16的可选前缀,且规范为

  

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

对于字符串"0xz",我认为预期格式的最长初始子序列为"0",因此值应为0且应设置endptr参数到x

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

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

cygwin-gcc 3.4.4(据我所知使用newlib)如果使用ERANGE,也不会解析文字,但是根据我对{{1}标准的解释来解析字符串}。

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

如果x失败,我不确定ungetc()应该做什么。也许只是设置流的错误指示符?

答案 3 :(得分:1)

总结解析数字时根据标准应该发生的事情:

  • 如果fscanf()成功,则结果必须与通过strto*()获得的结果相同
  • strto*()相比,fscanf()如果

    则失败
      

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

    根据fscanf()的定义不是

      

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

    根据strto*()

  • 的定义

这有点难看,但是要求fscanf()应该贪婪,但不能推回多个角色的必然结果。

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

  • strto*()无法使结果保持一致是愚蠢的( bad mingw
  • 推回多个字符,因此fscanf()接受strto*()接受的所有值都违反了标准,但是有理由(如果他们没有strto*(),则为newlib欢呼:(
  • 没有推回不匹配的字符,但仍然只解析'期望形式'的字符似乎可疑,因为字符消失得无影无踪(坏glibc

答案 4 :(得分:0)

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

答案 5 :(得分:0)

在重写问题后回答过时。评论中有一些有趣的链接。


  

如有疑问,请写一个测试。 - 谚语

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

当三种情况相遇时会出现差异:

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

    #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()函数在一致性方面表现相同。)

    所以...一个字符ungetc()并不是真正的原因,这里......?: - /

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

答案 6 :(得分: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()如何相互干扰。对不起,如果我添加了一个天真的评论。

答案 7 :(得分:0)

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

其余行为(例如 errno )应该是特定于实现的。例如,在我的FreeBSD框中,我得到 EINVAL ERANGE 值,在Linux下也会出现相同的情况,标准引用仅限于 ERANGE 错误值