注意:我完全重写了这个问题,以更恰当地反映我为此设置的赏金。请原谅与已经给出的答案有任何不一致之处。我不想创建一个新问题,因为之前的答案可能会有所帮助。
我正在努力实现C标准库,并对标准的一个特定角落感到困惑。
该标准根据scanf
的定义{{1}定义strtol
函数系列(%d,%i,%u,%o,%x)接受的数字格式}和strtoul
。
标准还说strtod
只会在输入流中放回最多一个字符,因此fscanf()
,strtol
和{{1}接受一些序列strtoul
是不可接受的(ISO / IEC 9899:1999,脚注251)。
我试图找到一些会表现出这种差异的价值观。事实证明,十六进制前缀“0x”后跟一个不是十六进制数字的字符,就是两个函数族不同的情况。
有趣的是,很明显没有两个可用的C库似乎在输出上达成一致。 (请参阅本问题末尾的测试程序和示例输出。)
我想听到的是在解析“0xz”时会被视为符合标准的行为?。理想情况下引用标准中的相关部分来说明问题。
strtod
答案 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
接受base
或16
的可选前缀,且规范为
主题序列定义为 最长的初始后续序列 输入字符串,从第一个开始 非空白字符,即 预期的形式。
对于字符串"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欢呼:()答案 4 :(得分:0)
我不确定我是否理解这个问题,但有一件事,scanf()应该处理EOF。 scanf()和strtol()是不同种类的野兽。也许你应该比较strtol()和sscanf()?
答案 5 :(得分:0)
在重写问题后回答过时。评论中有一些有趣的链接。
如有疑问,请写一个测试。 - 谚语
在测试了转换说明符和输入变量的所有组合后,我可以想到,两个函数族不能给出相同的结果是正确的。 (至少在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;
}
输出:
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 错误值