22
scanf(" %[^\n]",line);

我的一个朋友建议,使用fgets()读取一行作为输入会比使用scanf()上述语句中的 as 更好。他有道理吗?

4

8 回答 8

25

char * fgets ( char * str, int num, FILE * stream );使用安全,因为它避免了缓冲区溢出问题,它只扫描num-1字符数。

从流中读取字符并将它们作为 C 字符串存储到 str 中,直到读取 (num-1) 个字符或到达换行符或文件结尾,以先发生者为准。

这里第二个参数num是要复制到 str 中的最大字符数(包括终止的空字符)。

例如,假设在您的代码中,字符串数组的容量只是5字符长,如下所示。

 char str[5];
 fgets (str, 5, fp);  //5 =you have provision to avoid buffer overrun 

使用上面的代码,如果输入 from 比字符fp4fgets()将只读取第一个4字符然后附加\0( ,并丢弃其他额外的输入字符,只在 中存储五个字符str[])。

scanf(" %[^\n]",str);将读取直到\n找不到,并且如果输入字符串较长,则4charsscanf()将导致缓冲区溢出(因为scanf将尝试访问超出最大索引4in 的内存str[])。

于 2013-06-25T10:25:22.723 回答
9

C FAQ有一些关于scanf's 问题的详细解释:

更一般地说,scanf它是为相对结构化的格式化输入而设计的(它的名字实际上来源于“扫描格式化”)。如果你注意,它会告诉你它是成功还是失败,但它只能告诉你它失败的大致位置,而不是如何或为什么失败。您几乎没有机会进行任何错误恢复。

请参阅此处了解详细信息。

于 2013-06-25T10:28:23.403 回答
3

fgets会比这更好scanfscanfOP中给出的可能存在以下问题

1)@Grijesh 建议的缓冲区溢出

2)可能scanf在这之后的下一个将不起作用,因为换行符留在输入流中。(如果你错过了一个空格)

于 2013-06-25T10:31:32.380 回答
3

简单地说:是的,fgets是更好的选择。

我看了你的scanf格式说明符,我很困惑。准确理解它的作用需要一些时间阅读这些man页面。

此外,您的scanf代码容易受到缓冲区溢出的影响。

保持简单,您将降低维护成本并避免难以发现的错误!

于 2013-06-25T10:26:18.207 回答
3

是的 fgets 是从标准输入读取一行的更好和安全的方法。

此外,代码将具有更高的可读性。看你给的scanf语句。

任何第二个人看到它都会彻底糊涂。但是 fgets 会有更多的可读性,而且很容易理解。

于 2013-06-25T10:32:15.303 回答
1

使用 scanf() 读取一行不好?

主要的反对意见scanf(some_format, buffer)是缺乏缓冲区溢出保护,如

scanf(" %[^\n]",line);  // Bad - no buffer overflow protection.

另一种方法可以使用

char buffer[100];
scanf(" %99[^\n]",line);  // a little better

然而,它可以读取多条前导行(如果仅由空白组成),删除前导空白,缺乏处理非常量缓冲区宽度的简单方法,并且它将行的其余部分(可能只有'\n'. stdin如果第一个字符是 ,则不会立即返回'\n'


fgets()更好。

但它仍然存在问题:超过(示例)99 的行仍然存在,并且很难检测到stdin读取嵌入的空字符。

char buffer[100];
if (fgets(buffer, sizeof buffer, stdin)) {
  buffer[strcspn(buffer, "\n")] = '\0';  // Lop off potential tailing \n

代码可以根据需要使用fgetc()和制作代码来处理所有情况。然而,一次读取 1 个字符会导致显着的性能开销。


非标准 C 库getline() 相当流行。

一个缺点:它确实允许用户导致代码消耗过多的资源:过长的行会分配大量的内存。

char *line = NULL;
size_t len = 0;
ssize_t nread;

while ((nread = getline(&line, &len, stdin)) != -1) {

C 标准库缺少强大的get-line函数。最接近的是fgets().

scanf()IMO,在你知道它为什么不好之前不要使用。

于 2020-03-03T00:03:11.413 回答
1

好吧,恕我直言,这是因为scanf它不会强迫您限制输入大小,但fgets确实如此。

如果您阅读了文档并scanf正确使用,那么这里确实没有太大区别:

char line[256];

scanf("%255[^\n]%*c",line); // %*c to remove trailing \n
fgets(line, 256, stdin)

请注意,我从scanf格式字符串中删除了前导空格。 我稍后会回到这个。

这两种情况都确保我们不会阅读更多内容。

但是,从安全的角度来看,我们需要考虑:

  • fgets强制你指定尺寸
  • scanf你需要记住设置 array_capacity - 1
    • fgets为您执行此操作,因此您实际上传递了容量(或更少)
  • “伙计,这种格式到底是什么意思??”

很容易忘记 scanf 的细节,当你与一个拥有不同编程背景的庞大团队打交道时,有些人可能会更难以编写包含这些细节的代码,甚至可能不理解格式字符串。所以总的来说使用fgets比较安全。


现在,关于我删除的前导空格。

当您使用 时fgets,您将无法whitespace在输入前忽略字符,因此我不得不删除该空格以使两个调用具有几乎相同的结果。

我想我们不能真的说一种方式比另一种方式“更好”,只有这种fgets方式更具可读性,并且可以确保您记住传递大小。您还可以通过将scanf调用封装到正确构建格式字符串的输入读取函数中来实现这一点。这也可以让您whitespace在阅读前跳过前导字符。


编辑很清楚,我的观点是,从安全角度来看(如“不在字符数组之外写入”),这两种解决方案都是有效的,因为您可以限制在它们上读取的字符数。

我显示的代码纯粹是为了表明您可以将其限制为一定的大小,而不是它们具有完全相同的效果。
正如Andrew Henle所说,%*c如果用户提供的字符多于我们说要读取的长度,则确实会丢弃一个输入字符。
但是话又说回来,这不是我的意思,也不是问题,恕我直言。我只是把它放在那里更接近什么fgets,因为如果输入不大于您尝试读取的数量,则会从缓冲区fgets中删除。\n

据我所知,这个问题是关于scanfvsfgets的,并没有特别的意图。
至少有一个问题没有被描述。

当然,您需要考虑许多额外的事情,具体取决于应用程序需要执行的操作。

一些注意事项fgets

  • 如果您的输入有 length size - 1\n将留在缓冲区中
    (这会弄乱下一个字符输入......)
  • 如果您的输入有 length < size\n将被插入到字符串
    中(您需要将其删除,因为您可能不需要它)
  • 如果您的输入有 length > size,其余字符将留在缓冲区
    中(您也需要以某种方式处理它)
  • 如果更改数组长度,则需要在调用时更新大小
  • 在提取数据之前不能忽略空格

一些注意事项scanf

  • 您需要决定是否可以%*c安全地执行此操作
    (老实说,最好不要使用%*c,但在读取字符时在格式字符串上有一个前导空格,因为您已经在问题的代码上使用了)
  • 您需要记住设置capacity - 1宽度说明符
  • 如果更改数组长度,则需要更新说明width
  • 在提取数据之前可以忽略空格
  • 大多数人不知道比%s,更复杂的格式说明符%d,因此团队中的其他人可能很难理解代码
  • 如果输入长度与width您指定的匹配,\n将留在缓冲区
  • 如果输入长度大于width,剩余的字符也会留在输入缓冲区

无论如何,可能还有更多场景需要考虑,其中许多取决于您需要处理的确切情况。

这两种功能各有利弊。我不认为它们中的任何一个本质上都是“坏的”,它们都要求用户正确使用它们并自己处理一些错误,但是,fgets肯定具有迫使您提供长度和更具可读性的优势。

我希望我的观点现在更清楚了。

于 2019-10-30T12:04:28.490 回答
-1

不要使用 fgets(...) 而是使用以下代码段:

char _x[7000];
    char* y;
while ( ! feof (_f) )
{
    fscanf(_f,"%[^\n]\n",_x);
            y=x;
}
于 2014-03-08T11:17:41.723 回答