与C中scanf()中的[]转换说明符的逻辑不一致

时间:2013-05-27 17:37:17

标签: c format-specifiers

请看一下这段代码:

char line1[10], line2[10];
int rtn;
rtn = scanf("%9[a]%9[^\n]", line1, line2);
printf("line1 = %s|\nline2 = %s|\n", line1, line2);
printf("rtn = %d\n", rtn);

输出:

$ gcc line.c -o line
$ ./line
abook
line1 = a|
line2 = book|
rtn = 2
$./line
book
line1 = |
line2 = �Js�|
rtn = 0
$

对于输入abook%9[a]的{​​{1}}失败,b的{​​{1}}失败,book + a \0 line1 }。
然后%9[^\n]解析剩余的行,并在book处解析刚刚解析的\0 + line2。 请注意2点:

  1. 在存储已解析的输入时,\0附加在其末尾,因为%[]是字符串的转换说明符。
  2. %9[a] b失败时, scanf未退出。它只是继续扫描进一步的输入。
  3. 现在输入book%9[a] b的{​​{1}}失败,而book \0只会line1 %9[^\n]被解析了。 然后book应该解析剩下的行,并且应该在\0处刚刚解析line2 + scanf

    现在,让我们看看究竟发生了什么:
    返回值为0表示scanf没有为任何变量赋值。只需退出line2而不指定任何值。所以line1的垃圾数据。在NULL的情况下,垃圾数据碰巧是scanf个字符。

    但这很奇怪!不是吗? 我的意思是%[...]退出,如果scanf在输入的第一个字符处失败。 (即使%[...]声明中有其他转换说明符。)
    但是,如果相同的scanf在第一个字符以外的任何其他字符处失败,那么scanf只会继续扫描其他输入。 (如果当然有额外的转换说明符。)它不会退出。

    那为什么会出现这种不一致? 为什么不让%[...]语句继续扫描输入(如果当然还有其他转换说明符),即使$ gcc --version gcc (Ubuntu 4.4.3-4ubuntu5.1) 4.4.3 在第一个输入字符处失败?完全像在其他情况下发生的事情 这种不一致背后有什么特殊原因吗?

    {{1}}

2 个答案:

答案 0 :(得分:5)

  

2)当%9[a]在b失败时,scanf没有退出。它只是继续扫描进一步的输入。

是的,%9[a]指令意味着“存储最多 9 'a',但至少一个(1) ,所以转换%9[a]没有失败,它成功了。它发现'a'的消耗量比消耗的少'b',但这不是失败。输入匹配[处失败,但转换成功。

(1)在7.21.6.2(12)中指定转换的描述:

  

%9[a]匹配一组预期字符( scanset )中非空序列的字符。

  

现在对于输入书,'\0'应该从书中失败,并且应该只在{1}}存储在第1行,因为这里没有解析任何内容。然后%9[^\n]应该解析剩余的行,并且应该刚刚在第2行存储解析的book+\0

没有。它应该在转换失败时退出。第一次转换%9[a]失败,因此scanf应该停止并返回0,因为没有转换成功。

始终检查scanf的返回值。

在7.21.6.2(16)中指定了(fscanf,但scanf相当于fscanfstdin为输入流):

  

如果发生输入失败,fscanf函数将返回宏EOF的值   在第一次转换(如果有)之前完成。 否则,函数返回   分配的输入项目数量,可以少于提供的数量,甚至为零   早期匹配失败的事件。

     

这里line1的输出并不是我们所期望的。一个空字符串!

你不能指望任何事情。数组line1line2未初始化,因此当转换失败时,其内容仍然是不确定的。在这种情况下,line1在前0字节之前不包含可打印字符。

  

但对于line2来说,它是垃圾字符!我们没想到这一点。那怎么发生呢?

这就是line2的内容。从来没有为元素分配任何值,因此它们是在调用scanf之前发生的任何事情。

答案 1 :(得分:1)

从评论转移到问题,因为对回复问题的回复需要比评论允许的更多空间。

此评论涉及代码的早期版本:

  

由于你没有检查scanf()的返回值,你不知道它是否说“我失败了”。当你忽略它的错误返回时,你不能责怪它;在第二个例子中,它会说'0项成功扫描',这意味着没有任何变量被设置为任何有用的东西。您必须始终检查scanf()的返回值,以便了解它是否符合预期。

回复问题是:

  

我更新了代码并输出以显示scanf的返回值。对于案例2,是的,返回值为0.但这不回答问题。显然,scanf在案例2中退出。但对于案例1,返回值为2,这意味着scanf成功地为这两个变量赋值。那么为什么会出现这种不一致呢?

我没有看到任何不一致。 fscanf()规范(从ISO / IEC 9899:2011复制,但URL链接到POSIX而不是C标准)说:

  

¶3[...]每个转换规范都由字符%引入。   在%之后,以下顺序出现:

     

- 可选的赋值抑制字符*    - 大于零的可选十进制整数,指定最大字段宽度   (字符)。
   - 可选的长度修饰符,用于指定接收对象的大小    - 转换说明符字符,用于指定要应用的转换类型。

后来,它说:

  

¶8[...]跳过输入空白字符(由isspace函数指定),除非   该规范包括[cn说明符。 284)

     

¶9除非规范包含n说明符,否则将从流中读取输入项。一个   输入项被定义为输入字符的最长序列,不超过   任何指定的字段宽度,它是匹配输入序列的前缀,或者是匹配输入序列的前缀。 285)   输入项目之后的第一个字符(如果有)仍未读取。 如果输入的长度   item为零,指令的执行失败 ;这种情况是匹配失败,除非   文件结束,编码错误或读取错误阻止了流的输入,其中   如果是输入失败。

     

¶12[...]

     

[匹配一组预期字符中的 非空 字符序列   (扫描集)。 286)

[加粗斜体重点。我已经留下了脚注参考资料,但脚注的内容对讨论来说并不重要,所以我省略了它们。]

因此,您所看到的行为正是标准所要求的。当%9[a]应用于字符串abook时,会有一个a的序列与%9[a]转换规范匹配,因此该指令成功,并继续扫描book。当%9[a]应用于字符串book时,与该项匹配的零字符,因此指令的执行失败并且它是匹配的错误,因为它是第一个转换规范,返回值0是正确的。

请注意,长度指定了最大字段宽度,因此9中的%9[a]表示1-9个字母a