在解析的数字scanf()的和与strtol()/的strtod()之间差(Difference b

2019-07-18 01:28发布

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


我工作的实施C标准库,并感到困惑的标准的一个特定角落。

该标准定义了由所接受的数字格式scanf函数族(%d,%I,%U,%O,%X)在用于定义方面strtolstrtoulstrtod

该标准还指出, fscanf()将只放回最多一个字符的进入输入流,并且因此,通过接受一些序列strtolstrtoulstrtod是不能接受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
*/

Answer 1:

和Fred J. Tydeman,PL22.11副炭(ANSI“C”),通信上comp.std.c阐明这一些光:

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应该不分配值,则返回零(如果%x%i是第一个转换次数符),并留下“Z”作为输入流中的第一个未读字符。

strtol

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

主题序列被定义为输入串的最长的初始子序列,从第一个非空白字符, 也就是预期的形式的 。 (7.20.1.4 P4,重点煤矿)

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



Answer 2:

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



Answer 3:

按照C99规范中, scanf()系列函数解析整数方式相同strto*()函数家族。 例如,对于在转换说明x此读取:

匹配任选符号的十六进制整数,其格式是相同的预期的标的序列strtoul功能与用于将值16 base参数。

所以,如果sscanf()strtoul()给出不同的结果,在libc实现不符合。

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

strtoul()接受的可选的前缀0x0X如果base16 ,并且该规范读

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

对于字符串"0xz" ,在我看来有望形式的最长的初始序列是"0" ,因此该值应该是0endptr参数应设置为x

的mingw-GCC 4.4.0异议和失败,既分析字符串strtoul()sscanf() 其理由可能是期望形式的最长的初始序列是"0x" -这是不是一个有效的整数文字,所以没有解析完成。

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

cygwin的-GCC 3.4.4(这是据我所知使用newlib)也将无法解析,如果字面strtoul()按我的标准的解释被使用,但解析字符串sscanf()

要注意的是我的标准的解释是容易出现的问题initital,即该标准只保证能够ungetc()一次。 以决定是否0x是字面的一部分,你必须先读两个字符:在x和下面的字符。 如果它没有十六进制字符,它们必须推后。 如果有更多的令牌解析,可以缓冲它们并解决此问题,但如果它是最后一个记号,你必须ungetc()两个字符。

我真的不知道什么样fscanf()应该做的,如果ungetc()失败。 也许只是设置流的错误指示?



Answer 4:

总之应根据分析数字时的标准发生什么:

  • 如果fscanf()成功,结果必须与通过所获得的一个strto*()
  • 相反, strto*() fscanf()则会失败

    输入字符的最长序列[...]其是,或是一个前缀,一个匹配的输入序列

    根据定义fscanf()是不

    最长的初始后继[...]这是预期的形式

    根据定义strto*()

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

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

  • strto*()未能在结果相一致是愚蠢的( 坏的MinGW)
  • 推回一个以上的性格让fscanf()接受所接受的所有值strto*()违反了标准,但是是有道理的( 华友世纪newlib如果他们不把事情弄糟strto*() :()
  • 不推回不匹配的字符,但仍只解析“有望形成”的那些看起来可疑的人物成为泡影( 坏的glibc)


Answer 5:

我不知道我理解的问题,但对一分件事的scanf()应该EOF处理。 scanf()的和与strtol()是不同种野兽。 也许你应该比较与strtol()和sscanf()呢?



Answer 6:

我不知道如何实现scanf()函数可以ungetc函数来进行相关的()。 scanf()的可以在流缓冲器用完所有的字节。 ungetc函数()简单地推动一个字节缓冲器的端部和所述偏移也被改变。

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

如果输入是“100”时,输出为“100,9”。 我不知道怎样的scanf()和ungetc函数()可以相互干扰。 很抱歉,如果我添加了一个天真的评论。



Answer 7:

对于输入到scanf()函数,并且还为与strtol()函数,在二段。 7.20.1.4 P7表示: 如果主题序列为空或不具有预期的形式,不进行转换; NPTR的值被存储在对象指向endptr,条件是endptr是不是一个空指针 。 你也必须考虑解析其根据SEC规则定义的那些令牌的规则 6.4.4常量 ,即指向规则 7.20.1.4 P5。

行为的其余部分,如errno值,应该是实现特定的。 例如,在我的FreeBSD box我EINVALERANGE值和Linux下发生同样的情况,只有标准的引荐哪里来的ERANGE错误值。



Answer 8:

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


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

测试转换说明,我能想到的输入变化的所有组合后,我可以说,这是正确的,这两个功能的家庭不给相同的结果 。 (至少在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()函数的行为相同的一致性。)

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

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



文章来源: Difference between scanf() and strtol() / strtod() in parsing numbers