scanf()和strtol()/strtod()在解析数字方面的差异

scanf()和strtol()/strtod()在解析数字方面的差异,c,standards,standards-compliance,C,Standards,Standards Compliance,注意:我完全修改了这个问题,以更恰当地反映我设置奖金的目的。请原谅与已经给出的答案之间可能产生的任何不一致。我不想提出一个新问题,因为以前对这个问题的回答可能会有所帮助 我正在致力于实现一个C标准库,对该标准的一个具体方面感到困惑 本标准根据strtol、strtoul和strtod的定义定义了scanf功能系列(%d,%i,%u,%o,%x)所接受的数字格式 该标准还规定,fscanf()最多只能将一个字符放回输入流,因此strtol、strtoul和strtod接受的某些序列是fscanf

注意:我完全修改了这个问题,以更恰当地反映我设置奖金的目的。请原谅与已经给出的答案之间可能产生的任何不一致。我不想提出一个新问题,因为以前对这个问题的回答可能会有所帮助


我正在致力于实现一个C标准库,对该标准的一个具体方面感到困惑

本标准根据
strtol
strtoul
strtod
的定义定义了
scanf
功能系列(%d,%i,%u,%o,%x)所接受的数字格式

该标准还规定,
fscanf()
最多只能将一个字符放回输入流,因此
strtol
strtoul
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
*/
#包括
#包括
#包括
int main()
{
int i,计数,rc;
未签名的u;
char*endptr=NULL;
罪魁祸首[]=“0xz”;
/*断言fscanf==sscanf的文件I/O*/
文件*fh=fopen(“测试文件”,“w+”);
fprintf(fh,“%s”,罪犯);
倒带(fh);
/*fscanf基地16*/
u=-1;计数=-1;
rc=fscanf(fh、%x%n、&u和计数);
printf(“fscanf:返回%d,结果%2d,消耗%d\n”,rc,u,count);
倒带(fh);
/*斯特图尔基地16*/
u=strtoul(罪魁祸首和endptr,16);
printf(“strtoul:结果%2d,消耗%d\n”,u,endptr-罪魁祸首);
认沽权(“”);
/*fscanf基0*/
i=-1;计数=-1;
rc=fscanf(fh、%i%n、&i和计数);
printf(“fscanf:Returned%d,result%2d,consumered%d\n”,rc,i,count);
倒带(fh);
/*strtol基0*/
i=strtol(罪魁祸首和endptr,0);
printf(“strtoul:结果%2d,消耗%d\n”,i,endptr-罪魁祸首);
fclose(fh);
返回0;
}
/*新图书馆1.14
fscanf:返回1,结果0,消耗1
strtoul:结果0,消耗0
fscanf:返回1,结果0,消耗1
strtoul:结果0,消耗0
*/
/*glibc-2.8
fscanf:返回1,结果0,消耗2
strtoul:结果0,消耗1
fscanf:返回1,结果0,消耗2
strtoul:结果0,消耗1
*/
/*微软MSVC
fscanf:返回0,结果-1,已使用-1
strtoul:结果0,消耗0
fscanf:返回0,结果0,消耗-1
strtoul:结果0,消耗0
*/
/*IBM AIX
fscanf:返回0,结果-1,已使用-1
strtoul:结果0,消耗1
fscanf:返回0,结果0,消耗-1
strtoul:结果0,消耗1
*/

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

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

重写问题后,答案就过时了。不过注释中有一些有趣的链接


如果有疑问,写一个测试谚语

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

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

  • 您可以使用
    “%i”
    “%x”
    (允许十六进制输入)
  • 输入包含(可选)
    “0x”
    十六进制前缀
  • 十六进制前缀后面没有有效的十六进制数字
  • 示例代码:

    #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;
    }
    
    这使我困惑。显然,
    sscanf()
    不能在
    'x'
    处退出,否则它将无法解析任何前缀为十六进制的
    “0x”
    。因此,它读取了
    'z'
    ,发现它不匹配。但它决定只使用前导的
    “0”
    作为值。这意味着将
    'z'
    'x'
    推回。(是的,我知道
    sscanf()

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


    是,结果不同。尽管如此,我仍然无法正确解释-(

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

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

    因此,如果
    sscanf()
    strtoul()
    给出不同的结果,那么libc实现就不符合要求<
    Value: 0 - Consumed: 1 - Next char: x - (sscanf())
    Value: 0 - Consumed: 0 - Next char: 0 - (strtoul())
    
    scanf("%d", &x);
    ungetc('9', stdin);
    scanf("%d", &y);
    printf("%d, %d\n", x, y);