scanf的缺点
我想知道scanf的缺点,c,input,user-input,scanf,C,Input,User Input,Scanf,我想知道scanf()的缺点 在许多网站上,我读到使用scanf可能会导致缓冲区溢出。这是什么原因?scanf是否还有其他缺点?scanf的问题(至少): 使用%s从用户处获取字符串,这可能导致字符串可能比缓冲区长,从而导致溢出 扫描失败导致文件指针处于不确定位置的可能性 我非常喜欢使用fgets来读取整行数据,这样可以限制读取的数据量。如果您有一个1K缓冲区,并使用fgets将一行读入其中,您可以通过没有终止换行符(尽管没有换行符的文件的最后一行)来判断该行是否太长 然后,您可以向用户投诉
scanf()
的缺点
在许多网站上,我读到使用
scanf
可能会导致缓冲区溢出。这是什么原因?scanf是否还有其他缺点?scanf的问题(至少):
- 使用
%s
从用户处获取字符串,这可能导致字符串可能比缓冲区长,从而导致溢出
- 扫描失败导致文件指针处于不确定位置的可能性
我非常喜欢使用fgets
来读取整行数据,这样可以限制读取的数据量。如果您有一个1K缓冲区,并使用fgets
将一行读入其中,您可以通过没有终止换行符(尽管没有换行符的文件的最后一行)来判断该行是否太长
然后,您可以向用户投诉,或者为该行的其余部分分配更多空间(如有必要,可以连续分配,直到您有足够的空间为止)。在这两种情况下,都没有缓冲区溢出的风险
一旦你读入这一行,你就知道你在下一行,所以那里没有问题。然后,您就可以sscanf
将您的字符串保存到您的核心内容,而无需保存和恢复文件指针以便重新读取
下面是一段代码,我经常使用它来确保在询问用户信息时不会出现缓冲区溢出
如果需要,它可以很容易地调整为使用标准输入以外的文件,您也可以让它在将缓冲区返回给调用方之前分配自己的缓冲区(并不断增加它,直到它足够大为止)(当然,调用方将负责释放它)
最后,进行一次测试运行以显示其实际效果:
$ printf "\0" | ./tstprg # Singular NUL in input stream.
Enter string>
No input
$ ./tstprg < /dev/null # EOF in input stream.
Enter string>
No input
$ ./tstprg # A one-character string.
Enter string> a
OK [a]
$ ./tstprg # Longer string but still able to fit.
Enter string> hello
OK [hello]
$ ./tstprg # Too long for buffer.
Enter string> hello there
Input too long [hello the]
$ ./tstprg # Test limit of buffer.
Enter string> 123456789
OK [123456789]
$ ./tstprg # Test just over limit.
Enter string> 1234567890
Input too long [123456789]
$printf“\0”|./tstprg#输入流中的单数。
输入字符串>
没有输入
$./tstprg
没有输入
$./tstprg#一个字符串。
输入string>a
好的[a]
$./tstprg#更长的字符串,但仍然能够适应。
输入string>hello
好的[你好]
$./tstprg#缓冲区太长。
在那里输入string>hello
输入太长[hello the]
$./tstprg#缓冲区的测试限制。
输入字符串>123456789
好的[123456789]
$./tstprg测试刚刚超出限制。
输入字符串>1234567890
输入太长[123456789]
是的,你说得对。在scanf
系列(scanf
,sscanf
,fscanf
等)中存在一个主要的安全漏洞,尤其是在读取字符串时,因为它们没有考虑缓冲区的长度(它们正在读取的缓冲区)
例如:
char buf[3];
sscanf("abcdef","%s",buf);
显然,缓冲区buf
可以容纳MAX3
char。但是sscanf
会试图将中的“abcdef”
放入其中,导致缓冲区溢出。来自comp.lang.c常见问题解答:
scanf
有许多问题,请参见问题和。另外,它的%s
格式与get()
格式存在相同的问题(请参见问题)-很难保证接收缓冲区不会溢出
更一般地说,scanf
是为相对结构化、格式化的输入而设计的(其名称实际上来源于“扫描格式化”)。如果你注意,它会告诉你它是成功了还是失败了,但它只能告诉你失败的大致位置,而不能告诉你失败的方式或原因。您几乎没有机会执行任何错误恢复
然而,交互式用户输入是结构化程度最低的输入。一个设计良好的用户界面将允许用户键入任何内容,不仅仅是字母或标点符号(当需要数字时),还可以键入比预期更多或更少的字符,或者根本没有字符(即,仅返回键),或者过早输入EOF,或者任何内容。使用scanf
时,几乎不可能优雅地处理所有这些潜在问题;阅读整行文字(使用fgets
或类似工具)然后使用sscanf
或其他一些技术对其进行解释要容易得多。(像strtol
、strtok
和atoi
这样的函数通常很有用;另请参见问题和。)如果确实使用了任何scanf
变量,请确保检查返回值,以确保找到了预期数量的项目。此外,如果使用%s
,请确保防止缓冲区溢出
顺便说一下,对scanf
的批评不一定是对fscanf
和sscanf
的控诉scanf
读取stdin
,stdin通常是一个交互式键盘,因此受到的约束最小,导致的问题最多。另一方面,当数据文件具有已知格式时,可以使用fscanf
读取它。使用sscanf
解析字符串非常合适(只要检查了返回值),因为很容易重新获得控制权,重新启动扫描,如果输入不匹配则丢弃输入,等等
其他链接:
参考文献:K&R2第。下午7.4。159
很难让scanf做你想做的事情。当然,你可以,但是像是scanf(“%s”,buf)代码>和得到的一样危险(buf)代码>,正如大家所说
例如,paxdiablo在其函数to read中所做的工作可以通过以下方式完成:
scanf("%10[^\n]%*[^\n]", buf));
getchar();
上述操作将读取一行,将前10个非换行字符存储在buf
中,然后丢弃所有字符,直到(包括)换行。因此,paxdiablo的函数可以使用scanf
以下方式编写:
#include <stdio.h>
enum read_status {
OK,
NO_INPUT,
TOO_LONG
};
static int get_line(const char *prompt, char *buf, size_t sz)
{
char fmt[40];
int i;
int nscanned;
printf("%s", prompt);
fflush(stdout);
sprintf(fmt, "%%%zu[^\n]%%*[^\n]%%n", sz-1);
/* read at most sz-1 characters on, discarding the rest */
i = scanf(fmt, buf, &nscanned);
if (i > 0) {
getchar();
if (nscanned >= sz) {
return TOO_LONG;
} else {
return OK;
}
} else {
return NO_INPUT;
}
}
int main(void)
{
char buf[10+1];
int rc;
while ((rc = get_line("Enter string> ", buf, sizeof buf)) != NO_INPUT) {
if (rc == TOO_LONG) {
printf("Input too long: ");
}
printf("->%s<-\n", buf);
}
return 0;
}
如果发生溢流,则无法安全使用上述装置。即使在第一种情况下,读取字符串也是非常困难的
scanf("%10[^\n]%*[^\n]", buf));
getchar();
#include <stdio.h>
enum read_status {
OK,
NO_INPUT,
TOO_LONG
};
static int get_line(const char *prompt, char *buf, size_t sz)
{
char fmt[40];
int i;
int nscanned;
printf("%s", prompt);
fflush(stdout);
sprintf(fmt, "%%%zu[^\n]%%*[^\n]%%n", sz-1);
/* read at most sz-1 characters on, discarding the rest */
i = scanf(fmt, buf, &nscanned);
if (i > 0) {
getchar();
if (nscanned >= sz) {
return TOO_LONG;
} else {
return OK;
}
} else {
return NO_INPUT;
}
}
int main(void)
{
char buf[10+1];
int rc;
while ((rc = get_line("Enter string> ", buf, sizeof buf)) != NO_INPUT) {
if (rc == TOO_LONG) {
printf("Input too long: ");
}
printf("->%s<-\n", buf);
}
return 0;
}
int i;
scanf("%d", &i);
char *buf;
scanf("%ms", &buf); // with 'm', scanf expects a pointer to pointer to char.
// use buf
free(buf);
int i;
scanf("%10s", &i);
scanf("%10s", i);