如何确定使用fgets()函数读取的字符数?

10

这是从man页中关于fgets()的描述:

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

RETURN VALUE
  fgets() returns s on success, and NULL on error or  when  end  of  file
  occurs while no characters have been read.

它不遵循read的模式,后者在失败时返回-1,在成功时返回读取的字节数。相反,它返回一个char*,在失败时为NULL,在成功时为s。这并没有告诉我输入有多长。所以如果我有这样的东西:

char input_buffer[256];
fgets(input_buffer, sizeof(input_buffer), stdin);
在调用 fgets 后,是否有任何方法可以在不进行零初始化缓冲区的情况下确定输入的长度?
谢谢。

2
可能是fgets()的返回值重复问题 - Dumbo
2
int len= strlen(input_buffer); 这个语句将会给你输入缓冲区的长度。 - Paul Ogilvie
这就是为什么我不喜欢 fgets() 的设计,我更喜欢 fread(),但它不能处理本地化。 - Stargateur
2
很高兴知道 strlen 可以解决问题,但是有没有不涉及 O(n) 迭代的解决方案呢?似乎很浪费时间,因为 fgets 已经知道长度了。 - James Ko
@Stargateur,fread()不会在输入末尾添加NUL,但是fgets()会。因此,strlen()适用于fgets()但不适用于fread()。每个函数都有其目的和能力。函数fgets()被编写为可以“链接”。而函数fread()则不行。函数fgets()用于文本输入。函数fread()用于二进制输入。 - user3629249
@user3629249 谢谢你的明显指出。但是,我不喜欢fgets()函数中int类型的大小参数,也不喜欢需要使用strlen()来知道字符串的大小。getline()函数更好,但只适用于posix系统,fread()函数不会添加空字符,但你可以很容易地添加。就像我所说的,fread()fgets()在本地化、空字符等方面并不相同,但它具有更好的设计。 - Stargateur
2个回答

10
如何确定使用fgets()读取了多少个字符?
char *fgets(char *s, int size, FILE *stream);

在检查fgets()返回值后,使用 strlen(s)

if (fgets(s, size, stream)) {
  printf("number of characters that were read: %zu\n", strlen(s));
} else if (feof(stream)) {
  printf("number of characters that were read:0 End-of-file\n");
} else  {
  printf("number of characters that were read unknown due to input error\n");
}

除非读取到空字符'\0',否则此方法有效,因为函数会将后续的字符追加到字符串最后。但如果读取到了'\0',那么strlen()在函数之前就会遇到空字符'\0',导致其返回较小的值。

有各种技巧可以预先填充s,然后调用fgets(),但未读取缓冲区中剩余内容时会出现不确定情况,还存在其他缺陷。

如果担心输入流中包含空字符,则应使用fgetc()或类似的getline()方法。

当文本以UTF-16编码时,空字符往往是文本中的一部分。当然,代码不应该使用fgets()来读取这些文本,但这需要提前知道。由于错误地假定文本文件是非空字符的文本文件,许多读取文本的代码已以神秘方式失败。

此外,即使文本文件不包含空字符,下面的代码会发生什么呢?

if (fgets(s, size, stream)) {
  size_t len = strlen(s);
  s[--len] = '\0';  // poor way to lop off the trailing \n, this could be UB
}

这种代码在文件开头的一行中插入一个null字符,会触发未定义行为,从而被黑客利用。 (有关更好的解决办法,请参见此处此处以剪掉潜在的\n

健壮的代码不假设文本格式良好,并采取措施检测异常。


严谨的注释:使用fgets(char *s, int size, FILE *stream);时,如果size < 2,可能会出现病态问题。


1
对于许多重要的事情,通常被大部分人忽略了,包括我在内,你真是太棒了! - David C. Rankin

4
是的,有。在成功的情况下,它总是以空字符结尾。因此,长度将为strlen(buf)
来自标准7.21.7.2

char *fgets(char * restrict s, int n,FILE * restrict stream); fgets函数从指向stream的流中读取最多比n指定的字符数少一个字符,并将其读入指向s的数组中。在新行字符(保留)或文件结束后,不会读取其他字符。在数组中写入一个空字符,紧接着最后一个读取的字符。


2
当然,前提是输入本身不包含空字符,否则这个方案就会被破坏。但这是一个常见的限制。 - John Bollinger
@JohnBollinger:嗯,是的,那是一种情况,但为了避免这种情况,我们还可以做另一个检查(虽然也不完全可靠),即检查“\n”? - user2736738
1
是的,检查换行符可以完成任务,前提是提供给fgets()的缓冲区足够大,可以容纳整行,包括换行符。在许多情况下,假定或要求输入不包含空值比假定或要求输入不包含长行更安全。值得注意的是,fgets()最适合于文本,因此假定输入不包含空值通常是一个相当低的门槛。 - John Bollinger
2
@JohnBollinger 如果您的文本预计包含空终止符,则fgets可能不应该是您读取函数的首选。 - SergeyA
@JohnBollinger:所以假设,如果输入包含空值并且行不足以容纳\n,那么我猜这些都不会起作用(这很不可能)。 - user2736738
显示剩余2条评论

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