fgets()函数是否会在超过最大长度时自动追加\n\0字符?

3

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

char user[16];

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


2
在这种情况下,\n将保留在流中。它会在下一次读取时出现。 - Fred Larson
1
你的缓冲区应该声明为 char user[16]; - 5gon12eder
哎呀,对不起大家!是我不好! :) - Atlas80b
1
回答你的最后一个问题:不,将没有0字节的字符数组传递给printf并不保证会导致段错误。 - mafso
4个回答

8
作为对5gon12eder答案的补充,我假设您有以下内容:
char user[16];

fgets(user, 16, stdin);

假设你的输入是abcdefghijklmno\n,那就有15个字符和一个换行符。

fgets函数将会把输入的前15(16-1)个字符加上一个空字符放进user中,最终你将得到"abcdefghijklmno",这也正是你想要的。

但是... \n仍然留存在输入缓冲区中,并可以在同一文件上的下一次读取(无论是fgets还是其他任何操作)时被使用。换句话说,在你执行另一个fgets之前,你无法知道是否还有其他字符跟随着o


现在一切都清楚了,非常感谢您的示例。我运行了一些其他测试,最终明白了如何正确处理fgets()函数! - Atlas80b

5

C99标准(N1256)中的fgets文档

7.19.7.2 fgets函数

概述

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

描述

fgets函数从由stream指向的流中最多读取n个字符,然后将其存储到由s指向的数组中。新行字符(保留)或文件末尾后面不会再读取任何其他字符。一个空字符会被写入到数组中最后一个字符读取完成之后立即。

针对您的问题,您说:

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

对于这种情况,换行符直到下一次调用fgets或任何其他从流中读取的调用时才会被读取。

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

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


你提供的“fgets文档”链接并不权威。 - R.. GitHub STOP HELPING ICE
@R. 对了,我记起来了。在最终版本之前的巴塞罗那会议上,他们对fgets进行了临时修复,因为有一个关键性的缺陷报告。谢谢你指出这一点,很有帮助。 - Peter - Reinstate Monica
非常感谢!所以在使用新的fgets()之前,我还需要“清除”stdin流。 - Atlas80b
@Toxicroak,欢迎。您所说的“clear”,是指丢弃该行剩余部分吗? - R Sahu
是的,那就是我想表达的意思 :) - Atlas80b

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

现在进入细节:

'\n' 和 '\0' 不会自动添加,只有 '\0' 会自动添加。fgets() 函数在读取到 '\n' 时停止读取,但也可能因为缓冲区已满等其他原因而停止读取,在这些情况下,'\n' 不存在于 '\0' 前面。
fgets() 不是读取 C 字符串,而是读取一行文本,输入流通常处于文本模式,进行换行转换。在某些系统上,'\r\n' 将被翻译成 '\n',而在其他系统上则不会。通常情况下,文件读取匹配这种转换,但也会有例外。在二进制模式下,没有任何转换发生。
fgets() 会读入 '\0',然后继续读取。因此使用 strlen(buf) 并不能总是反映读取的字符数。虽然可以有一种确定中间存在 '\0' 时读取真实字符数的方法,但使用 fread() 或 fgetc() 更容易编写代码。
当 EOF 条件(且未读取数据)或 I/O 错误时,fgets() 返回 NULL。当发生 I/O 错误时,缓冲区的内容未定义。
C 标准使用 int 类型作为缓冲区大小,但通常代码传递 size_t 类型的变量。小于 1 或大于 INT_MAX 的大小可能会出现问题。大小为 1 应该仅填充 buf[0] = '\0',但某些系统行为不同,特别是在接近或超过 EOF 条件时。但只要 2 <= n <= INT_MAX,则可以期望有终止的 '\0'。注意:当 size 太小时,fgets() 可能会返回 NULL。
代码通常希望删除终止符 '\n',但使用某些代码可能会导致问题。建议使用以下代码:
```c 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'; ```
健壮代码检查 fgets() 的返回值。下面的方法存在缺陷:
1. 如果发生 I/O 错误,缓冲区内容未定义。检查缓冲区内容将无法提供可靠的结果。 2. '\0' 可能是读取的第一个字符,并且文件不在 EOF 条件下。
```c // 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(); } ```

1
我已经实现了行末删除器,但是谢谢你提供有关EOF/IO错误的提示,这非常有帮助^^ - Atlas80b

2

fgetsman页面中:

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

fgets() reads in at most one less than size characters from stream and stores them into the buffer pointed to by s. Reading stops after an EOF or a newline. If a newline is read, it is stored into the buffer. A terminating null byte ('\0') is stored after the last character in the buffer.

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


我之前在使用一个在线手册时,fgets的定义不同了... 现在感觉有点傻,哈哈。谢谢! - Atlas80b

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接