题
我想知道的缺点 scanf()
.
在许多地方,我已经阅读,使用 scanf
可能导致缓冲区溢出。是什么原因?是否有任何其他的缺点 scanf
?
解决方案
问题scanf是(至少):
- 使用
%s
得到一串户,这导致这种可能性,串可以更长的时间比你的缓冲,导致溢出。 - 可能失败的扫描离开你的文件指针在一个不确定的位置。
我非常喜欢用 fgets
阅读整个线,让你可以限制的数量数据的读取。如果你已经有了一个1K的缓冲,你读一线进入它 fgets
你可以告诉我们,如果该线过长的事实没有终止newline character(最后一个文件没有一个newline尽管).
然后你可以抱怨的用户,或分配更多的空间进行的其余部分(不断,如果有必要的,直到你有足够的空间)。在任何一种情况下,没有风险的缓冲溢出。
一旦你读过线了,你 知道吗 你们在定位在下一个线,所以没有问题。然后你可以 sscanf
你的串到你的心内容,而不必保存和恢复该文件指针,用于重新阅读。
这是一段代码我经常使用,以确保没有缓冲区溢出时,询问用户的信息。
它可以方便地调整以使用一个文件比其他标准输入,如果有必要,你还可以拥有它分配其自己的缓冲区(和不断增加,直到它的足够大)之前,回到呼叫者(虽然该呼叫者将负责释放,当然)。
#include <stdio.h>
#include <string.h>
#define OK 0
#define NO_INPUT 1
#define TOO_LONG 2
#define SMALL_BUFF 3
static int getLine (char *prmpt, char *buff, size_t sz) {
int ch, extra;
// Size zero or one cannot store enough, so don't even
// try - we need space for at least newline and terminator.
if (sz < 2)
return SMALL_BUFF;
// Output prompt.
if (prmpt != NULL) {
printf ("%s", prmpt);
fflush (stdout);
}
// Get line with buffer overrun protection.
if (fgets (buff, sz, stdin) == NULL)
return NO_INPUT;
// If it was too long, there'll be no newline. In that case, we flush
// to end of line so that excess doesn't affect the next call.
size_t lastPos = strlen(buff) - 1;
if (buff[lastPos] != '\n') {
extra = 0;
while (((ch = getchar()) != '\n') && (ch != EOF))
extra = 1;
return (extra == 1) ? TOO_LONG : OK;
}
// Otherwise remove newline and give string back to caller.
buff[lastPos] = '\0';
return OK;
}
而且,一个试验驾驶员:
// Test program for getLine().
int main (void) {
int rc;
char buff[10];
rc = getLine ("Enter string> ", buff, sizeof(buff));
if (rc == NO_INPUT) {
// Extra NL since my system doesn't output that on EOF.
printf ("\nNo input\n");
return 1;
}
if (rc == TOO_LONG) {
printf ("Input too long [%s]\n", buff);
return 1;
}
printf ("OK [%s]\n", buff);
return 0;
}
最后,测试运行来表明它在行动:
$ ./tstprg
Enter string>[CTRL-D]
No input
$ ./tstprg
Enter string> a
OK [a]
$ ./tstprg
Enter string> hello
OK [hello]
$ ./tstprg
Enter string> hello there
Input too long [hello the]
$ ./tstprg
Enter string> i am pax
OK [i am pax]
其他提示
大多数的答案迄今为止似乎侧重于字符串缓冲区的溢出的问题。在现实中,格式符,可与 scanf
职能明确的支持 场的宽度 设置限制的最大尺寸的输入和防止缓冲区的溢出。这使得流行的指控串-危险缓冲区溢出存在 scanf
几乎毫无根据的。声称 scanf
某种程度上是类似于 gets
在这方面是完全不正确的。还有一个重大的质的差别 scanf
和 gets
: scanf
不用户提供string-缓冲区溢出防功能,同时 gets
不。
一个可以争辩说,这些 scanf
功能是难以使用,因为该领域的宽度必须嵌入格式string(有没有办法通过它通过一个可变的论点,因为它可以做的 printf
).这实际上是真实的。 scanf
确实是而设计不当,在这方面的工作。但尽管如此,任何要求, scanf
是某种无可救药的破关于字符串的缓冲区溢出安全是完全虚假的和通常的懒惰的程序员。
真正的问题 scanf
有一个完全不同的性质,虽然它也是 溢出.时 scanf
功能用于转换的小数表示的数字值的算术种类,它没有提供任何保护,从算术溢出。如果溢出发生, scanf
产生未定义的行为。由于这个原因,唯一的适当方式进行的转换在C的标准图书馆职能是从 strto...
家庭。
因此,要总结上述的问题 scanf
是,这是困难的(尽管是可能的)正确使用和安全地与串的缓冲区。这是不可能安全地使用,用于算术的输入。后者是真正的问题。前者仅仅是一个带来的不便。
P.S.上述目的是对整个家庭的 scanf
功能(包括也 fscanf
和 sscanf
).与 scanf
具体而言,显而易见的问题是,非常的想法的使用严格式的功能,用于阅读的可能 互动 输入是相当令人怀疑。
从比赛。郎。c常见问题: 为什么每个人都说不要使用scanf?我应该使用什么呢?
scanf
有一些问题—见的问题 12.17, 12.18一个, , 12.19.此外,它的%s
格式具有相同的问题,gets()
有(见问题 12.23)—它很难保证接受的缓冲区不会溢出。 [脚注]更一般地说,
scanf
设计用于比较的结构、格式的输入(其名称实际上是在来自"格式的扫描").如果你注意,它将告诉你它是否成功或失败,但是它可以告诉你只有大约在那里失败,并不是所有的如何或为什么。你很少有机会做任何错误恢复。然而交互式用户输入的至少输入结构化。一个精心设计用户接口将允许对于可能的用户输入任何事情—不只是字母或标点时的数字是预期的,但也或多或少符于预期、或不符,在所有(即, 只是返回的关键),或过早EOF,或任何东西。它几乎不可能处理的优雅与所有这些潜在的问题的时候使用
scanf
;它远远更容易阅读整个线(与fgets
或类似的),然后解释他们,要么使用sscanf
或者一些其他的技术。(功能喜欢strtol
,strtok
, ,atoi
往往是有用的;也参看问题 12.16 和 13.6.) 如果您使用的任何scanf
变种,一定要检查返回的价值,以确保预期的项目数被发现。此外,如果您使用%s
, ,确保为防止缓冲区的溢出。注意,通过该方式,那些批评的
scanf
不一定起诉书的fscanf
和sscanf
.scanf
读取stdin
, ,这通常是一个交互式键盘和因此最少的限制,导致大多数问题。当一个数据文件都有一个已知的格式,另一方面,它可能是适当的阅读它fscanf
.这是完全适当的分析字sscanf
(只要返回值是检查),因为它是如此容易重新获得控制权,重新启动扫描,丢弃的输入,如果它没有比赛,等等。额外的链接:
参考文献:K&R2。7.4p.159
这是非常难得到的 scanf
要做的事情你想要的。当然,你可以,但是喜欢的东西 scanf("%s", buf);
都是危险的,因为 gets(buf);
, 如大家所说的话。
作为一个例子,什么paxdiablo是在做他的功能阅读可以做的东西,如:
scanf("%10[^\n]%*[^\n]", buf));
getchar();
上述会读一线,储存的第一个10个非newline字 buf
, ,然后丢弃的一切,直到(并包括)newline.因此,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
是其行为的情况下溢出。例如,当读一个 int
:
int i;
scanf("%d", &i);
上面不能安全使用的情况下一个溢出。即使对于第一种情况下,读一串是更简单的做 fgets
而不是用 scanf
.
是的,你是正确的。还有一个重大的安全漏洞 scanf
家庭(scanf
,sscanf
, fscanf
等等)esp当读一串,因为他们不采取缓冲区的长度(成它们的阅读)考虑在内。
例如:
char buf[3];
sscanf("abcdef","%s",buf);
明确的缓冲 buf
可以住MAX 3
char。但 sscanf
将尝试把 "abcdef"
成它导致缓冲区的溢出。
问题我有 *scanf()
家庭:
- 潜在缓冲区溢出与%s%[转换符。是的,你可以指定最高领域的宽度,但不像
printf()
, 你不能让这一说法的scanf()
话;它必须是硬编码在转换说明符。 - 潜在用于算术的溢出%d%我,等等。
- 有限的能力来检测和拒绝严重形成的投入。例如,"12w4"不是一个有效的整,但是
scanf("%d", &value);
将成功地转换和分配12value
, ,而使"w4"停留在输入流臭立一个未来阅读。理想的整个输入串应被拒绝,但是scanf()
不会给你一个简单的机制来这样做。
如果你知道你的输入是始终要以及形成与固定长串和数值,不调情溢出,然后 scanf()
是一个伟大的工具。如果你正在处理的交互式输入或输入,不能保证可以形成良好,再使用别的东西。
许多答案在这里讨论潜在的溢出问题的使用 scanf("%s", buf)
, 但最新的POSIX规范或多或少可以解决此问题通过提供一个 m
分配分配角色,可以使用的格式的说明符 c
, s
, , [
格式。这将允许 scanf
分配尽可能多的内存作为必要用 malloc
(所以它必须被释放后面 free
).
一个例子,其使用:
char *buf;
scanf("%ms", &buf); // with 'm', scanf expects a pointer to pointer to char.
// use buf
free(buf);
看看 在这里,.缺点,这种方法是,它是一个相对较新的外POSIX说明书中并未指定在C的规范,所以它仍然是,而无法移植。
有一个很大的问题 scanf
-像功能的缺乏 任何 类型的安全。也就是说,你可以代码这样的:
int i;
scanf("%10s", &i);
地狱,甚至这是"罚款":
scanf("%10s", i);
它比 printf
-像功能,因为 scanf
预计指针,因此崩溃的可能性更大。
当然,有一些格式的说明符跳出了那里,但是,这些都不是完美的,嗯,它们不是部分的语言或标准图书馆。
优点 scanf
是一旦你学会了如何使用的工具,因为你总是应该做C,它具有非常有用的用例. 你可以学习如何使用 scanf
和朋友们通过阅读和理解 该手册.如果你不能通过这一手册,而没有严重的理解问题,这可能会表明你不知道C很好。
scanf
和朋友遭受了不幸的设计的选择 这呈现的困难(偶尔也不可能)采用正确地没有阅读的文件,其他的答案。这发生在整个C,不幸的是,如果我们要建议反对使用 scanf
然后我大概会建议反对使用C.
最大的一个缺点似乎是纯粹的声誉,这是获得在门外汉;如同许多有用的特征C我们应充分了解之前,我们使用它。关键是要认识到,作为与其他C,看来简洁和习惯用语,但可以巧妙地误导。这是普遍存在C;这很容易,对于初学者编写代码,他们认为有意义的,甚至可能会为他们工作的初期,但是没有意义的和可能失败灾难性的。
例如,对新手来说通常期待 %s
委托将会导致 一线 要读,同时可能似乎直观,也不一定是真实的。这是更为恰当描述的领域中读作 一个字.阅读手册,是强烈建议每一个功能。
会是什么任回答这个问题是没有提到其缺乏安全和风险的缓冲溢?正如我们已经复盖,C并不是一个安全的语言,并将使我们能偷,可能适用的优化费用的正确或更可能是因为我们懒惰的程序员。因此,当我们知道,该系统将永远不会收到一串大于一个固定的数字,我们定能够宣布一系列大小和放弃边界检查。我真的不把这看作一个下下降;这是一个选项。再次,阅读该手册是强烈建议,并将揭示这一选择给我们。
懒并不是唯一的刺痛了 scanf
.这是不常看到人们试图读取 float
或 double
值的使用 %d
, 例。他们通常错误地认为执行将执行的某种转换在幕后,这将是有意义的,因为类似的转换发生的整个其余部分的语言,但这不是这里的情况。正如我早些时候所指出的那样, scanf
和朋友(和事实上的其余部分C)欺骗性;他们看起来简洁和习惯用语,但他们不是。
没有经验的程序员都不是不得不考虑操作的成功.假设用户输入的东西完全无数的时候,我们已经告诉 scanf
阅读并将其转换序列的小数点位数使用 %d
.只有这样,我们可以拦截这样的错误数据,以检查返回的价值,我们经常打扰检查返回的价值?
很像 fgets
, 时 scanf
和朋友们无法读取是什么他们告诉读,流将留在一个不寻常的状态;
-在情况下 fgets
, 如果没有足够的空间来保存一个完整的线,然后将剩余的行左读可能会被错误地当作这是一个新的线的时候,它不是。-在情况下 scanf
和朋友,一个转换失败的记录上,错误的数据是左读上流,并可能被错误地视为它的一部分,一个不同的领域。
它没有容易使用 scanf
和朋友比使用 fgets
.如果我们检查取得成功通过在寻找一个 '\n'
当我们使用 fgets
或者通过检查返回的价值,当我们使用 scanf
和朋友,和我们发现,我们已经阅读不完整行使用 fgets
或未能读一领域的使用 scanf
, 那我们面临着同样的现实:我们很可能会 弃输入 (通常直到和包括该next newline)!Yuuuuuuck!
不幸的是, scanf
两者同时使得它很难(非直观的)和容易(最少键击)放弃输入这种方式。面对这一现实,废弃用户输入,有些人试图 , 不明白的 scanf("%*[^\n]%*c");
%*[^\n]
委托将失败的时候遇到什么但有一个空行,因此行仍将留在流。
一个微小的适应,通过分离的两个格式的代表和我们看到一些成功: scanf("%*[^\n]"); getchar();
.试试这样做有这么几个按键使用一些其他工具;)