【问题标题】:Reading a line using scanf() not good?使用 scanf() 读取一行不好?
【发布时间】:2013-06-22 02:25:34
【问题描述】:
scanf(" %[^\n]",line);

我的一个朋友建议使用fgets() 来读取一行作为输入比使用scanf() (如上面的语句)要好得多。他有理由吗?

【问题讨论】:

  • 应该是 scanf(" %[^\n]",line);关闭参数中的大括号..
  • 他绝对是。我总是建议使用fgets() 并完全避免使用scanf()
  • @RaghuSrikanthReddy 为什么不使用scanf() 的完美例子。
  • fgets() 更好。但它更像scanf("%[^\n]",line);(格式中没有空格)。 OP 的" %[^\n]" 将消耗前导空格而不存储在line 中。 fgets() 存储前导空格(不是\n)。
  • 任何带有%s%[ 且没有长度说明符的scanf 都应被视为错误。现在,如果您指定长度,那将是一个有趣的问题。

标签: c scanf stdio


【解决方案1】:

char * fgets ( char * str, int num, FILE * stream ); 可以安全使用,因为它避免了buffer overflow 问题,它只扫描num-1 字符数。

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

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

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

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

使用上面的代码,如果来自fp 的输入更长,那么4 字符,fgets() 将首先读取4 字符然后附加\0 (,并丢弃其他额外的输入字符,只是在str[] 中存储五个字符。

scanf(" %[^\n]",str); 将一直读取直到找不到\n,如果输入字符串更长,则4 字符scanf() 将导致buffer overflow(因为scanf 将尝试访问超出最大索引@987654340 的内存@ 在str[])。

【讨论】:

  • 感谢您的详细解释。如果我不知道输入字符串的长度,scanf() 会是更好的选择吗?
  • @amulous : YuHao 先生给出了一个非常好的链接。读到那里,scanf()printf()系列函数对格式化输入/输出有好处
【解决方案2】:

C FAQscanf的问题做了一些详细的解释:

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

详情请参阅here

【讨论】:

    【解决方案3】:

    fgets 会比这个scanf 更好。 OP 中给出的scanf 可能存在以下问题

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

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

    【讨论】:

      【解决方案4】:

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

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

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

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

      【讨论】:

        【解决方案5】:

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

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

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

        【讨论】:

          【解决方案6】:

          好吧,恕我直言,这是因为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

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

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

          fgets 的一些注意事项:

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

          scanf 的一些注意事项:

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

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

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

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

          【讨论】:

          • 如果你给scanf() 输入比缓冲区长的行会怎样?那会丢弃一个无法恢复的角色,不是吗?
          • 确实如此。这就是为什么我使用“没有太大区别”(这意味着有一点区别)和“几乎相同的结果”。我稍后会将此信息添加到我的评论中,以便更加明确。但是我向您提出另一个问题:如果您输入 fgets 的字符少于指定的字符会怎样?它将在字符串中插入\n。所以这两种解决方案各有利弊。不过不要误会,我也比较赞成用fgets我就是觉得有些人真的不知道scanf的所有功能。例如,有些人甚至不知道您可以设置长度限制。
          • 使用scanf("%255[^\n]%*c",line); 并且读取的第一个 字符是'\n',没有任何内容被读取到lineline 保持原样,可能缺少空字符。 '\n' 保留在 stdin 中,如果再次调用此函数,可能会设置和无限循环。
          【解决方案7】:

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

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

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

          替代方法可以使用

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

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


          fgets() 更好。

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

          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()

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

          【讨论】:

            【解决方案8】:

            不要使用 fgets(...) 而是使用以下 sn-p:

            char _x[7000];
                char* y;
            while ( ! feof (_f) )
            {
                fscanf(_f,"%[^\n]\n",_x);
                        y=x;
            }
            

            【讨论】:

              猜你喜欢
              • 2023-01-21
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2013-07-04
              相关资源
              最近更新 更多