深入理解scanf()
动机
有人发给了我这样一段代码
int i=0;
do {
scanf("%d ", &array[i]);
sum[i] = sumdigits(array[i]);
i++;
} while (i < 10);
这段代码的本意是依次读入10个整数到数组中,但实际上输入10个整数后,无论你怎样按回车他都不会进入到下一个语句,除非你再输入一个任意的非空白字符。
所以,这是为什么?他看上去不该那样,对吗?
注意到,%d后面跟了一个空格。
分析
国内的教材大多非常粗浅,寥寥数语便讲解完这个函数。
下面我将和大家一起阅读POSIX对于scanf()
的标准文档,希望从中能找到问题的答案。
我尽可能保证我的翻译是正确的。
定义
#include <stdio.h>
int scanf(const char *restrict format, ...);
...
说明其参数数量未定。
解释
基本运作方式
scanf()
从标准输入(stdin)中读取字节, 根据格式解释它们,并将结果存储在对应变量中。参数应当包括一个格式化字符串( 原文:control string format|指代形参中的format),以及一组指针参数(这解释了为什么变量前面要加&运算符),转换后的输入被存入指代的位置。
如果指针参数的数量少于格式化字符串中的转换说明数量,那么结果是undefined(换而言之,不知道会发生什么,具体取决于编译器)
如果指针参数的数量少多格式化字符串中的转换说明数量,那么多余的指针参数仍然会被求值,但是会被忽略掉。
但不管怎么样,我们应该养成好的习惯,确保指针参数数量与转换说明数量一致。
格式
格式通过字符串的形式来描述,也就是所谓的格式化字符串。
格式化字符串又由零条或多条指令构成,每条指令又由下面的元素之一构成:
一个或多个空格字符
普通字符(既不是%
也不是空格)
转换说明
例如在表达式scanf("%d %d",&a,&b);
中%d
, ,
和
都是指令
返回值
成功完成后,函数应返回成功匹配和分配的输入项的数量
如果早期匹配失败,数字可以为零
如果输入在第一次转换完成之前结束,并且没有发生匹配故障,应返回EOF
如果在第一次转换完成之前发生错误,并且在未发生匹配故障的情况下,应返回EOF,并设置 errno 以指示错误
如果读取 发生错误时,应设置流的错误指示器
例子
scanf(" %2d", &i);
如果我们输入一堆空格加321加一堆空格,再换行, 程序实际所做的:
忽略前面的一堆空格,直到读取到3,并把它放回缓冲区
从缓冲区中拿出3再拿出2,这时已经达到最大字段宽度,不再继续读取
计算32的二进制值为0010 0000
计算表达式&i得到i的内存地址,然后将0010 0000存入。
此时缓冲区里还剩1加一堆空格加换行符
溢出问题
如果str在堆中申请的空间较小,使用scanf(“%s”,str)时,很容易发生溢出,这是 C 语言开发中最经常出现的 bug 之一,内存访问越界,而且不易排查。
导致的后果:
编程时需要自己多加注意。
为了一定程序避免解决这个问题,后面出现了 scanf 的衍生 scanf_s。
19 August 2024