fgets()函数是否附加超过最大长度的\ n \ 0字符?

时间:2014-10-01 21:08:16

标签: c fgets

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

char user[16];

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

4 个答案:

答案 0 :(得分:6)

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

char user[16];

fgets(user, 16, stdin);

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

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

但是...... \n仍然保留在流缓冲区中,实际上可以在同一个文件上进行下一次读取(可以是fgets或其他任何内容)。更确切地说,在您执行另一个fgets之前,您无法知道o之后是否还有其他字符。

答案 1 :(得分:5)

正如@ 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'就会停止阅读,但由于其他原因也会停止,包括完整的缓冲区。在这些情况下,'\n'之前没有'\0'

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

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

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

  5. 迂腐问题:C标准使用int类型作为缓冲区的大小,但通常代码传递类型为size_t的变量。大小n小于1或大于INT_MAX可能是个问题。大小为1 应该只能填充buf[0] = '\0',但有些系统的行为会有所不同,尤其是在EOF条件接近或通过时。但只要2 <= n <= 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();
    }
    

答案 2 :(得分:4)

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个字符将是终止空字符。

答案 3 :(得分:2)

来自fgets的{​​{3}}:

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

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

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