C语言 使用 scanf() 读取一行不好?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/17294809/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Reading a line using scanf() not good?
提问by amulous
scanf(" %[^\n]",line);
A friend of mine suggested that using fgets()to read a line as input would be a much better idea than using scanf()as in the statement above. Is he justified?
我的一个朋友建议使用fgets()读取一行作为输入比使用scanf()as 在上面的语句中使用要好得多。他有道理吗?
回答by Grijesh Chauhan
char * fgets ( char * str, int num, FILE * stream );is safe to use because it avoid buffer overflowproblem, it scans only num-1number of char.
char * fgets ( char * str, int num, FILE * stream );使用安全,因为它避免了缓冲区溢出问题,它只扫描num-1字符数。
Reads characters from stream and stores them as a C string into str until (num-1) characters have been read or either a newline or the end-of-file is reached, whichever happens first.
从流中读取字符并将它们作为 C 字符串存储到 str 中,直到读取 (num-1) 个字符或到达换行符或文件结尾,以先发生者为准。
here second argument numis Maximum number of characters to be copied into str (including the terminating null-character).
这里的第二个参数num是要复制到 str 中的最大字符数(包括终止空字符)。
For example suppose in your code a string array capacity is just 5chars long as below.
例如,假设在您的代码中,字符串数组容量仅为5字符长,如下所示。
char str[5];
fgets (str, 5, fp); //5 =you have provision to avoid buffer overrun
Using above code, if input from fpis longer then 4chars, fgets()will read just first 4chars then appends \0(, and discard other extra input chars, just stores five char in str[]).
使用上面的代码中,如果输入的fp是不再那么4字符,fgets()会读只是第一个4字符,然后追加\0(和丢弃其它多余输入字符,只是存储五个字符str[])。
Whereas scanf(" %[^\n]",str);will read until \nnot found and if input string is longer then 4chars scanf()will cause of buffer overflow(as scanfwill try to access memory beyond max index 4in str[]).
而scanf(" %[^\n]",str);将读取直到\n未找到,如果输入字符串较长,则4字符scanf()将导致缓冲区溢出(因为scanf将尝试访问超出 max index 4in 的内存str[])。
回答by Yu Hao
C FAQhas some detailed explanation about scanf's problem:
C FAQ有一些关于scanf's 问题的详细解释:
More generally,
scanfis designed for relatively structured, formatted input (its name is in fact derived from "scan formatted"). If you pay attention, it will tell you whether it succeeded or failed, but it can tell you only approximately where it failed, and not at all how or why. You have very little opportunity to do any error recovery.
更一般地说,
scanf是为相对结构化、格式化的输入而设计的(它的名字实际上来源于“扫描格式化”)。如果您注意,它会告诉您它是成功还是失败,但它只能告诉您失败的大致位置,而根本无法告诉您失败的原因或原因。您几乎没有机会进行任何错误恢复。
see herefor detail.
详情请看这里。
回答by Dayal rai
fgetswill be better than this scanf.
There may be following issues with scanfas given in OP
fgets会比这更好scanf。scanfOP 中可能存在以下问题
1)buffer overflow as suggested by @Grijesh
1)@Grijesh 建议的缓冲区溢出
2)possibly next scanfafter this won't work because the newline is left in the input stream.(if you miss a blank space)
2)可能scanf在此之后的下一个将不起作用,因为换行符留在输入流中。(如果你错过了一个空格)
回答by Kranthi Kumar
Yes fgets is the better and safe way to read a line from the standard input.
是的 fgets 是从标准输入读取一行的更好和安全的方法。
Moreover there will more readability in the code. Look at the scanf statement given by you.
此外,代码的可读性也会更高。看你给的scanf语句。
Any second person who sees it will be thoroughly confused. But whereas there will be more readeablity for fgets and it is easy to understand.
任何第二个人看到它都会彻底糊涂。但是 fgets 会有更多的可读性并且很容易理解。
回答by Klas Lindb?ck
Simply put: yes, fgetsis a better choice.
简单地说:是的,fgets是更好的选择。
I looked at your scanfformat specifier and I was mystified. Understanding exactly what it does requires some time reading through the manpages.
我看着你的scanf格式说明符,我很困惑。准确了解它的作用需要花一些时间阅读man页面。
Also, your scanfcode is susceptible to buffer overruns.
此外,您的scanf代码容易受到缓冲区溢出的影响。
Keep it simple and you will reduce the maintenance costs and avoid hard to find bugs!
保持简单,您将降低维护成本并避免难以发现的错误!
回答by Leonardo Louren?o
Well, IMHO this is due to the fact that scanfdoesn't force you to limit the input size, but fgetsdoes.
好吧,恕我直言,这是因为scanf它不会强迫您限制输入大小,而是fgets会这样做。
If you read the documentation and use scanfproperly, there really isn't much difference here:
如果您阅读文档并scanf正确使用,这里确实没有太大区别:
char line[256];
scanf("%255[^\n]%*c",line); // %*c to remove trailing \n
fgets(line, 256, stdin)
Notice that I removed the leading space from the scanfformat string.I'll go back to this later.
请注意,我从scanf格式字符串中删除了前导空格。稍后我会回到这个话题。
Both cases ensure we don't read more than we can.
这两种情况都确保我们不会阅读更多。
But, from a safety perspective, we need to think:
但是,从安全的角度来看,我们需要考虑:
fgetsforce you to specify the size- On
scanfyou need to remember to set array_capacity - 1fgetsdoes this for you, so you actually pass in the capacity (or less)
- "Man what does this format even mean??"
fgets强制您指定大小- 在
scanf你需要记住一套array_capacity - 1fgets为你做这件事,所以你实际上传递了容量(或更少)
- “伙计,这种格式到底是什么意思??”
It's easy to forget scanf details, and when you deal with a vast team with different backgrounds in programming, some people might have more trouble to write code with such details and might not even understand the format string.
So in general using fgetsis safer.
很容易忘记 scanf 细节,当你与一个具有不同编程背景的庞大团队打交道时,有些人可能会更难编写包含这些细节的代码,甚至可能不理解格式字符串。所以一般来说使用fgets比较安全。
Now, regarding the leading space I removed.
现在,关于我删除的前导空间。
When you use fgets, you lose the ability to ignore whitespacecharacters before input, so I had to remove that space to make both calls have almost the same result.
当您使用 时fgets,您将失去whitespace在输入之前忽略字符的能力,因此我不得不删除该空格以使两个调用具有几乎相同的结果。
I guess we can't really say one way is "better" than the other, only that fgetsis more readable and will ensure that you remember to pass the size. Which is something you could also achieve by encapsulating the scanfcall into an input reading function that builds a format string correctly. Which would also enable you to skip leading whitespacecharacters before reading.
我想我们不能真的说一种方式比另一种“更好”,只有这样fgets更具可读性并且可以确保您记住传递大小。您也可以通过将scanf调用封装到正确构建格式字符串的输入读取函数中来实现这一点。这也将使您能够whitespace在阅读前跳过前导字符。
EDIT Just so it's clear, my point here is that from a safety perspective (as in "not writing outside the character array"), both solutions are valid since you can limit the number of characters read on both of them.
编辑 很明显,我的观点是,从安全的角度来看(如“不在字符数组之外写入”),这两种解决方案都是有效的,因为您可以限制读取的字符数。
The code I displayed is purely to show that you can have it limited to a certain size, not that they have exactly the same effect.
As Andrew Henlesaid, the %*cwill indeed throw away one input character if the user provides more characters than the length we said to read.
But then again, that wasn't my point here and neither the question, IMHO.
I just put it there to be a bit closer to what fgetsdoes since fgetsremoves the \nfrom the buffer if the input isn't bigger than the amount you are trying to read.
我显示的代码纯粹是为了表明您可以将其限制为特定大小,而不是它们具有完全相同的效果。
正如Andrew Henle所说,%*c如果用户提供的字符多于我们所说的读取长度,它确实会丢弃一个输入字符。
但话又说回来,这不是我的观点,也不是问题,恕我直言。我只是把它放在那里更接近什么fgets,因为如果输入不大于您尝试读取的数量fgets,\n则从缓冲区中删除。
The question asks about scanfvs fgetswith no particular intent in mind, as far as I understood.
At least one wasn't described on the question.
据我所知,这个问题是在没有特别意图的情况下询问scanfvs 的fgets。
至少一个问题没有描述。
There are a bunch of extra things you need to consider, depending on what the application need to do, of course.
当然,您需要考虑很多额外的事情,这取决于应用程序需要做什么。
Some considerations for fgets:
一些注意事项fgets:
- If your input has length
size - 1,\nwill be left in the buffer
(which will mess up next character inputs...) - If your input has length
< size,\nwill be inserted on the string
(which you will need to remove, since you probably won't need it) - If your input has length
> size, remaining characters will be left on the buffer
(which you will need to treat that somehow too) - If you change the array length, you need to update the size on the call
- Can't ignore whitespace before extracting data
- 如果您的输入有 length
size - 1,\n将留在缓冲区中
(这会弄乱下一个字符输入......) - 如果您的输入具有 length
< size,\n则将插入到字符串中
(您需要将其删除,因为您可能不需要它) - 如果您的输入有 length
> size,剩余的字符将留在缓冲区中
(您也需要以某种方式对待它) - 如果更改数组长度,则需要在调用时更新大小
- 在提取数据之前不能忽略空格
Some considerations for scanf:
一些注意事项scanf:
- You need to decide if you can do the
%*csafely or not
(Honestly, it's better to not use%*c, but just have a leading space on the format string when reading characters, as you already are using on the code on your question) - You need to remember to set
capacity - 1on the width specifier - If you change the array length, you need to update the
widthspecifier - Can ignore whitespace before extracting data
- Most people don't know format specifiers more complex than
%s,%dand such, so it might be hard for other people on the team to understand the code - If the input length matches the
widthyou specified,\nwill be left on the buffer - If the input length is greater than
width, remaining characters will be left on the input buffer too
- 您需要决定是否可以
%*c安全地执行此操作
(老实说,最好不要使用%*c,但在读取字符时在格式字符串上只留一个前导空格,因为您已经在问题的代码中使用了) - 您需要记住设置
capacity - 1宽度说明符 - 如果更改数组长度,则需要更新说明
width符 - 可以在提取数据之前忽略空格
- 大多数人不知道比
%s,%d等更复杂的格式说明符,因此团队中的其他人可能很难理解代码 - 如果输入长度与
width您指定的匹配,\n将留在缓冲区 - 如果输入长度大于
width,剩余的字符也将留在输入缓冲区
Anyway, there's probably many more scenarios to consider, and many of them depend on the exact situation you need to deal with.
无论如何,可能还有更多场景需要考虑,其中许多取决于您需要处理的确切情况。
Both functions have their pros and cons.
I don't think either of them is inherently "bad", both of them require the user to use them correctly and treat some errors themselves, but, fgetscertainly has the advantage of forcing you to provide a length and being more readable.
这两种功能各有优缺点。我不认为它们中的任何一个本质上都是“坏的”,它们都要求用户正确使用它们并自己处理一些错误,但是,fgets当然具有强制您提供长度和更具可读性的优点。
I hope my point is clearer now.
我希望我的观点现在更清楚了。
回答by chux - Reinstate Monica
Reading a line using scanf() not good?
使用 scanf() 读取一行不好?
The primary objection to scanf(some_format, buffer)is a lack buffer overflow protection as with
主要反对意见scanf(some_format, buffer)是缺乏缓冲区溢出保护,如
scanf(" %[^\n]",line); // Bad - no buffer overflow protection.
An alternative could use
另一种方法可以使用
char buffer[100];
scanf(" %99[^\n]",line); // a little better
Yet that can read multiple leading lines (if only made of white-space), drops leading white-space, lacks an easy way to handle non-constant buffer widths, and it leaves the rest of the line (maybe only '\n') in stdin. Does not return promptly if the first character was '\n'.
然而,它可以读取多个前导行(如果只由空格组成),删除前导空格,缺乏处理非常量缓冲区宽度的简单方法,并且它将行的其余部分(可能只有'\n')留在stdin. 如果第一个字符是 ,则不会立即返回'\n'。
fgets()is better.
fgets()更好。
It still remains with problems though: lines longer than (example) 99 remain in stdinand reading embedded null chractersare hard to detect.
尽管如此,它仍然存在问题:长度超过(示例)99 的行仍然存在stdin并且难以检测到读取嵌入的空字符。
char buffer[100];
if (fgets(buffer, sizeof buffer, stdin)) {
buffer[strcspn(buffer, "\n")] = 'char *line = NULL;
size_t len = 0;
ssize_t nread;
while ((nread = getline(&line, &len, stdin)) != -1) {
'; // Lop off potential tailing \n
Code could use fgetc()and crafted code to handle all cases as desired. Yet reading 1 character at a time incurs significant performance overhead.
代码可以fgetc()根据需要使用和制作代码来处理所有情况。然而,一次读取 1 个字符会导致显着的性能开销。
Non standard C library getline()is fairly popular.
非标准 C 库getline()相当流行。
One downside: It does allow the user to cause code to consume excessive resources: An overlong line can allocate huge amounts of memory.
一个缺点:它确实允许用户导致代码消耗过多资源:过长的行会分配大量内存。
char _x[7000];
char* y;
while ( ! feof (_f) )
{
fscanf(_f,"%[^\n]\n",_x);
y=x;
}
The C standard library lacks a robust get-linefunction. The closest is fgets().
C 标准库缺乏健壮的get-line函数。最接近的是fgets()。
IMO, do not use scanf()until you know why it is bad.
IMO,在scanf()您知道它为什么不好之前不要使用。
回答by mch
don't use fgets(...) instead use the following snippet:
不要使用 fgets(...) 而是使用以下代码段:
##代码##
