scanf(),字段宽度,inf和nan

时间:2017-06-04 11:03:27

标签: c parsing floating-point language-lawyer scanf

根据1999年的C标准,scanf()strtod()应接受无穷大和NaN作为输入(如果实现支持)。

两种功能的描述都有特殊的语言,可能会有解释。

scanf()

  

输入项被定义为输入字符的最长序列   它不超过任何指定的字段宽度,是或是   匹配输入序列的前缀。

strtod()

  

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

虽然后者摘录似乎严格要求特定形式的" INF"," INFINITY"," NAN"或者" NAN(n-char-sequence-opt)",前者不是,并且人们会认为以下代码应该产生无穷大和NaN,因为字段宽度涵盖了匹配输入序列的前缀:

int r;
double d;
d = 0; r = sscanf("inf", "%2le", &d);
printf("%d %e\n", r, d);
d = 0; r = sscanf("nan", "%2le", &d);
printf("%d %e\n", r, d);

scanf()上还有这个位:

  

a,e,f,g匹配一个可选的有符号浮点数,无穷大,   或NaN,其格式与主题序列的预期相同   strtod功能。相应的参数应为指针   浮动。

这是否仅仅是未能记录字段宽度为2,这比预期的最短形式(" inf"或#34; nan")更小匹配前缀" in"和" na"有效的匹配?

2 个答案:

答案 0 :(得分:4)

scanf的行为规范中,“输入项”恰好是格式说明符处理所消耗的输入字符序列。在处理格式说明符之后,无论该处理是否成功,流都精确地定位在输入项中的最后一个字符之后,如紧接在问题中引用的“输入项”的定义之后的句子所清楚的那样。

  

输入项目保持未读后的第一个字符(如果有)。

读取输入项后,scanf进入下一步(同一条款的第10段),其中输入项作为一个整体必须根据格式说明符:

  

10 ... [输入项]转换为适合转换说明符的类型。如果输入项不是匹配序列,则指令的执行失败:此条件是匹配失败。

“匹配序列”在每个格式说明符的描述中定义;对于f说明符将是:

  

strtod函数的主题序列的预期相同。

在问题中引用。

这与strtod使用的算法不同。 strtod找到最长的匹配序列,如果有一个(即使只有一个字符),它会转换它并将下一个字符的地址放在提供的endptr参数中。

相比之下,scanf必须处理输入流的限制,这不允许多个字符可靠地重写读指针。 (参见ungetc的定义。)所以scanf读取,直到找到一个无法扩展匹配的字符,此时它将该字符替换为输入流并尝试转换已读取的内容到那时。与strtod不同,它不能回溯到较短的有效序列(如果有的话)。

strtod的另一个区别是scanf可以限制为最大长度,对于转换无限长的固定长度输入字段非常有用。对于strtod,有必要制作固定长度字段的NUL终止副本,或者在适当的点临时插入NUL,然后恢复被覆盖的字符。在这种情况下,验证strtod消耗了整个输入是很重要的;如果没有,则表示输入中存在垃圾。

可以看到差异 可凭经验看出。给定输入1E-@scanf 报告匹配失败,后续getchar 返回'@'strtod1.0返回,endptr指向E

指定的格式长度也会导致scanf返回匹配的错误。给定输入1E-7@scanf格式%2f%3f ; %1f将转换1.0%4f(或更大)将转换.01,将@留给下一个说明符(或后续输入函数)。应用于%2finf的输入的nan应表现出与1E-7完全相同的行为:吸收两个字符后匹配失败(因为截断的字段不是有效的)浮点数。)。

上述是否发生取决于标准C库的实现是否符合标准。 Glibc没有,glibc很可能是你将在Linux平台上使用的,无论你是用gcc还是clang编译,因为clang没有捆绑标准的C库,甚至在libcxx项目中都没有。

我在Windows上进行的有限测试(使用在线编译器)表明scanf的libcrt实现确实可以正常工作。我自己对FreeBSD库的源代码的检查表明,scanf会正确地报告匹配的失败,但可能会将读取的光标向上移回多个字符。

答案 1 :(得分:2)

没有必要记录这个具体案例,因为它已经在标准的一个引号中涵盖了:

  

输入项被定义为输入字符的最长序列   它不超过任何指定的字段宽度,是或是   匹配输入序列的前缀。

考虑这个简单的案例:

int ch;
int res;
const char *input_fmt;

ch = 0;
input_fmt = "%*d%c";
res = sscanf("123456789012345678901234567890abc", input_fmt, &ch);
printf("%d\t%c\n", res, ch);

// 1      a

由于%d转换规范没有字段宽度,因此它将匹配无限多个字符0-9,这就是扫描完成后ch=='a'的原因; n的值在这里并不重要。与使用input_fmt="%*2d%c"的输出对比:

1      3

第一个转化规范的字段宽度限制为2个字符,因此%c匹配3。这是"前缀" bit进来。12123456...的前缀,与%d转换规范相匹配。另一个例子:

input_fmt = "%*2d%c";
res = sscanf("1a3456789012345678901234567890bc", input_fmt, &ch);
printf("%d\t%c\n", res, ch);

// 1      a

字段宽度将匹配%d的字符数限制为2,但匹配的输入序列长度为1,因为a不是十进制整数。

字段宽度限制匹配输入是您在infnan进行的操作:

double d;
int r;
char s[10];

// 0       0.000000        ""
*s = 0; d = 0; r = sscanf("infinity", "%2le%9s", &d, s);
printf("%d\t%f\t\"%s\"\n", r, d, s);

// 2       inf             "inity"
*s = 0; d = 0; r = sscanf("infinity", "%3le%9s", &d, s);
printf("%d\t%f\t\"%s\"\n", r, d, s);

// 0       0.000000        ""
*s = 0; d = 0; r = sscanf("-infinity", "%3le%9s", &d, s);
printf("%d\t%f\t\"%s\"\n", r, d, s);

// 2       -inf             "inity"
*s = 0; d = 0; r = sscanf("-infinity", "%4le%9s", &d, s);
printf("%d\t%f\t\"%s\"\n", r, d, s);

字段宽度限制输入序列中的字符数,这可能会阻止识别-infnan等特殊格式。该行为已被记录,但也无法立即完全理解。