【问题标题】:does the fgets() function append the \n\0 characters exceeding the maximum length?fgets() 函数是否附加超过最大长度的 \n\0 个字符?
【发布时间】:2014-11-26 20:15:06
【问题描述】:

对于大多数人来说,这似乎是一个愚蠢的问题,但我仍在努力确定最终答案。几个小时前,我决定用 fgets() 替换我项目中的所有 scanf() 函数,以获得更健壮的代码。 我了解到 fgets() 会自动以 '\n' 和 NUL 字符结束插入的输入字符串,但是.. 假设我有这样的事情:

char user[16];

存储用户名的 16 个字符的数组(最多 15 个字符,我为 NUL 终止符保留最后一个字符)。 问题是:如果我插入一个 15 个字符的字符串,那么 '\n' 将出现在数组的最后一个单元格中,但是 NUL 终止符呢? '\0' 是否存储在以下内存块中? (调用 printf() 函数时没有分段错误意味着插入的字符串实际上是 NUL 终止的,对吧?)。

【问题讨论】:

  • 在这种情况下,\n 将留在流中。它会在那里供您下次阅读。
  • 你的缓冲区应该声明为char user[16];
  • 呃,对不起各位!我的错! :)
  • 解决您的最后一个问题:不,将不带 0 字节的字符数组传递给 printf保证段错误。

标签: c fgets


【解决方案1】:

来自 C99 标准 (N1256) 的 fgets 文档

7.19.7.2 fgets函数

概要

#include <stdio.h>
char *fgets(char * restrict s, int n,
FILE * restrict stream);

说明

fgets 函数读取的字符数最多比n 指定的字符数少一个 从stream 指向的流到s 指向的数组。没有额外的 在换行符(保留)之后或文件结尾之后读取字符。一种 在读入数组的最后一个字符之后立即写入空字符。

来到你的帖子,你说:

存储用户名的 16 个字符的数组(最多 15 个字符,我为 NUL 终止符保留最后一个字符)。问题是:如果我插入一个 15 个字符的字符串,那么 '\n' 会出现在数组的最后一个单元格中,但是 NUL 终止符呢?

对于这种情况,在下一次调用 fgets 或任何其他从流中读取的调用之前,不会读取换行符。

'\0' 是否存储在以下内存块中? (调用 printf() 函数时没有分段错误意味着插入的字符串实际上是 NUL 终止的,对吧?)。

始终设置终止空字符。在您的情况下,第 16 个字符将是终止空字符。

【讨论】:

  • 您链接为“fgets 的文档”的网站不具有权威性。
  • @R。对了,现在想起来了。他们有这个关于 fgets 的严重缺陷报告,这是在最终版本之前的巴塞罗那会议上最后一刻采取的行动。感谢您指出这一点,这很有帮助。
  • 非常感谢!所以我还需要在使用新的 fgets() 之前“清除”stdin 流
  • @Toxicroak,不客气。 clear,你的意思是丢弃剩下的行吗?
  • 是的,我就是这个意思:)
【解决方案2】:

正如@5gon12eder 建议的那样,使用:

char user[16];
fgets(user, sizeof user, stdin);

// Function prototype for reference
#include <stdio.h>
char *fgets(char * restrict s, int n, FILE * restrict stream);

现在了解详情:

  1. '\n''\0' 不会自动附加。只有'\0' 会自动附加。 fgets() 将在获得 '\n' 后停止读取,但也会因其他原因(包括完整缓冲区)停止读取。在这些情况下,'\0' 之前没有'\n'

  2. fgets() 不读取 C 字符串,而是读取一行。输入流通常处于文本模式,然后发生行尾翻译。在某些系统上,'\r''\n' 对将转换为 '\n'。在其他人身上,它不会。通常正在读取的文件与此翻译匹配,但会出现异常。在二进制模式下,不会发生翻译。

  3. fgets() 读入'\0'。并继续阅读。因此使用strlen(buf) 并不总是反映char 读取的真实数量。当'\0'在中间时,可能有一种完全证明的方法来确定读取的char的真实数量,但使用fread()fgetc()可能更容易编码。

  4. 在 EOF 条件(并且没有读取数据)或 IO 错误时,fgets() 返回 NULL。发生 I/O 错误时,缓冲区的内容未定义。

  5. 迂腐问题:C 标准使用 int 类型作为缓冲区的大小,但代码通常会传递 size_t 类型的变量。小于 1 或大于 INT_MAX 的大小 n 可能是个问题。 1 的大小应该只是填充buf[0] = '\0',但某些系统的行为会有所不同,尤其是在 EOF 条件接近或通过时。但只要2 &lt;= n &lt;= INT_MAX,就可以期待终止'\0'。注意:fgets() 可能会在尺寸太小时返回NULL

  6. 代码通常喜欢删除终止 '\n' 的内容可能会导致麻烦。建议:

    char buf[80];
    if (fgets(buf, sizeof buf, stdin) == NULL) Handle_IOError_or_EOF();
    
    // IMO potential UB and undesired behavior
    // buf[strlen(buf)-1] = '\0';
    
    // Suggested end-of-line deleter
    size_t len = strlen(buf);
    if (len > 0 && buf[len - 1] == '\n') buf[--len] = '\0';
    
  7. 健壮的代码检查来自fgets() 的返回值。下面的方法有缺点。 1) 如果发生 IO 错误,则未定义缓冲区内容。检查缓冲区内容不会提供可靠的结果。 2) '\0' 可能是第一个 char 读取并且文件不在 EOF 条件下。

    // Following is weak code.
    buf[0] = '\0';
    fgets(buf, sizeof buf, stdin);
    if (strlen(buf) == 0) Handle_EOF();
    
    // Robust, but too much for code snippets
    if (fgets(buf, sizeof buf, stdin) == NULL) {
      if (ferror(stdin)) Handle_IOError();
      else if (feof(stdin)) Handle_EOF();
      else if (sizeof buf <= 1) Handle_too_small_buffer();  // pedantic check
      else Hmmmmmmm();
    }
    

【讨论】:

  • 我已经实现了行尾删除器,但是感谢您提供有关 EOF/IO-error 的提示,非常有帮助^^
【解决方案3】:

作为 5gon12eder 答案的补充。我假设你有类似的东西:

char user[16];

fgets(user, 16, stdin);

您的输入是 abcdefghijklmno\n ,即 15 个字符和一个换行符。

fgets 将输入 user 输入的 15 (16-1) 个第一个字符,后跟一个 null,您将有效地得到 "abcdefghijklmno",这就是您想要的

但是...\n 仍然保留在流缓冲区中,实际上可用于同一文件上的下一次读取(无论是fgets 还是其他任何内容)。更准确地说,在你再做一次fgets 之前,你无法知道o 后面是否还有其他字符。

【讨论】:

  • 现在一切都清楚了,非常感谢您的示例。我运行了一些其他测试,终于明白如何正确处理 fgets() 函数!
【解决方案4】:

来自fgetsman page

char *fgets(char *s, int size, FILE *stream);

fgets()stream 中读取最多小于 size 个字符,并将它们存储到 s 指向的缓冲区中。在 EOF 或换行符后停止读取。如果读取了换行符,则将其存储到缓冲区中。一个终止的空字节 ('\0') 存储在缓冲区中的最后一个字符之后。

我认为这很清楚,不是吗?

【讨论】:

  • 我使用的是在线手册,但 fgets 的定义不同.. 我现在觉得很愚蠢,哈哈。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-10-10
  • 2018-09-30
  • 1970-01-01
  • 2018-12-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多