注:我完全重新设计的问题更恰当地反映了我设置赏金。 请原谅这个可能已创建已经给出答案,任何不一致。 我不想创建一个新的问题,因为以前的答案,这样一个可能会有所帮助。
我工作的实施C标准库,并感到困惑的标准的一个特定角落。
该标准定义了由所接受的数字格式scanf
函数族(%d,%I,%U,%O,%X)在用于定义方面strtol
, strtoul
和strtod
。
该标准还指出, 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
*/
和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”,并返回零作为结果。
我不相信解析允许产生不同的结果。 该Plaugher参考只是指出的是, strtol()
的实现可能是不同的,更有效的版本,因为它对于整个字符串的完全访问权限。
按照C99规范中, scanf()
系列函数解析整数方式相同strto*()
函数家族。 例如,对于在转换说明x
此读取:
匹配任选符号的十六进制整数,其格式是相同的预期的标的序列strtoul
功能与用于将值16 base
参数。
所以,如果sscanf()
和strtoul()
给出不同的结果,在libc实现不符合。
什么,你预期的结果示例代码应该是有点不清楚,但是:
strtoul()
接受的可选的前缀0x
或0X
如果base
是16
,并且该规范读
主题序列被定义为输入串的最长的初始子序列,从第一个非空白字符,也就是预期的形式的。
对于字符串"0xz"
,在我看来有望形式的最长的初始序列是"0"
,因此该值应该是0
和endptr
参数应设置为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()
失败。 也许只是设置流的错误指示?
总之应根据分析数字时的标准发生什么:
这有点难看,但该需求的必然结果fscanf()
应该是贪婪的,但不能推回一个以上的字符。
一些库implementators选择了不同的行为。 在我看来
- 让
strto*()
未能在结果相一致是愚蠢的( 坏的MinGW) - 推回一个以上的性格让
fscanf()
接受所接受的所有值strto*()
违反了标准,但是是有道理的( 华友世纪newlib如果他们不把事情弄糟strto*()
:() - 不推回不匹配的字符,但仍只解析“有望形成”的那些看起来可疑的人物成为泡影( 坏的glibc)
我不知道我理解的问题,但对一分件事的scanf()应该EOF处理。 scanf()的和与strtol()是不同种野兽。 也许你应该比较与strtol()和sscanf()呢?
我不知道如何实现scanf()函数可以ungetc函数来进行相关的()。 scanf()的可以在流缓冲器用完所有的字节。 ungetc函数()简单地推动一个字节缓冲器的端部和所述偏移也被改变。
scanf("%d", &x);
ungetc('9', stdin);
scanf("%d", &y);
printf("%d, %d\n", x, y);
如果输入是“100”时,输出为“100,9”。 我不知道怎样的scanf()和ungetc函数()可以相互干扰。 很抱歉,如果我添加了一个天真的评论。
对于输入到scanf()函数,并且还为与strtol()函数,在二段。 7.20.1.4 P7表示: 如果主题序列为空或不具有预期的形式,不进行转换; NPTR的值被存储在对象指向endptr,条件是endptr是不是一个空指针 。 你也必须考虑解析其根据SEC规则定义的那些令牌的规则。 6.4.4常量 ,即指向秒规则。 7.20.1.4 P5。
行为的其余部分,如errno值,应该是实现特定的。 例如,在我的FreeBSD box我EINVAL和ERANGE值和Linux下发生同样的情况,只有标准的引荐哪里来的ERANGE错误值。
回答问题的重写之后过时。 在评论一些有趣的链接,但。
如有疑问,编写一个测试。 - 谚语
测试转换说明,我能想到的输入变化的所有组合后,我可以说,这是正确的,这两个功能的家庭不给相同的结果 。 (至少在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()
并不真正是原因,这里...: - /
是的, 结果不同 。 我仍然无法正常解释它,但... :-(