feof()函数如何知道文件何时结束?

7
我是C++的初学者,正在尝试更好地理解feof()函数。据我所知,只有在试图多次读取文件末尾后,feof()标志才会设置为true,因此如果像while(!feof(file))这样做,许多初学者将读取比他们预期的多一次。我想了解的是,它实际上如何解释已经尝试读取超过文件结尾?整个文件是否已经被读入,并且已知字符数,还是有其他机制在起作用?
我意识到这可能是重复的问题,但我找不到它,可能是因为我不知道询问的最佳方式。如果已经有答案,那么链接将不胜感激。谢谢。

1
假设有一个没有已知限制的流。那么,除了尝试读取文件末尾以外,没有其他方法。(请注意,文件流是一种用于各种输入/输出的抽象) - user2249683
1
@kartik7153 这并没有回答他的问题。他问的具体是操作系统如何确定已经到达文件结尾的时候。 - Creris
@Jake 只有在那个时候(仅在那个时候),文件的结尾才会被指示。 - user2249683
1
@Jake 那是操作系统的细节(它取决于所使用的底层设备/协议)。 - user2249683
1
我认为最终决定的是文件系统的实现方式,由于Linux支持不同的文件系统,因此需要查找它们的实现代码。 - alk
显示剩余3条评论
5个回答

11
无论C++库做了什么,最终它都必须从文件中读取。在操作系统的某个地方,有一段代码最终处理这个读取操作。它从文件系统获取文件的长度,以与文件系统存储其他所有内容相同的方式进行存储。知道文件的长度、读取位置和需要读取的字节数后,它可以确定低级读取是否到达文件尾部。
当做出这个决定时,它会向上传递到堆栈。最终,它到了标准库,标准库会在内部记录已经到达文件末尾。当一个读取请求试图超过记录的结尾时,EOF(End of File)标志被设置,并且 feof就会开始返回真值。

谢谢,这正是我想知道的! - Austin

8

feof()是标准C库中的一部分,用于缓冲I/O。由于它是缓冲的,fread()会预读取一些数据(肯定不是整个文件)。如果在缓冲时,fread()检测到EOF(底层操作系统例程返回一个特殊值,通常是-1),它会在FILE结构上设置一个标志。 feof()只是检查该标志。因此,feof()返回true基本上意味着“先前的读取尝试遇到了文件结尾”。

如何检测EOF是特定于操作系统/文件系统的,与C库/语言没有任何关系。操作系统具有从文件中读取数据的某些接口。 C库只是程序与操作系统之间的桥梁,因此如果您更改操作系统,则无需更改程序。操作系统知道如何在其文件系统中存储文件,因此知道如何检测EOF。 我猜通常是通过将当前位置与文件长度进行比较来执行此操作,但这可能并不容易,并且可能涉及许多低级细节(例如,如果文件位于网络驱动器上怎么办?)。

一个有趣的问题是当流已经结束,但尚未被任何读取操作检测到时会发生什么。例如,如果您打开一个空文件。在任何fread()之前,第一次调用feof()会返回true还是false?答案可能是false。文档在这个主题上并不十分清楚:

此指示通常由先前在流上尝试读取或超过文件结尾的操作设置。

听起来好像某个特定实现可能会选择其他不寻常的方式来设置此标志。

谢谢。你能不能详细说明一下,“如果在缓冲时,fread()检测到EOF”?我的问题是如何标记和检测EOF。 - Austin

3

大多数文件系统都保存有关文件的元信息(包括其大小),尝试读取超出文件末尾的部分会导致设置feof标志。其他一些旧的或轻量级文件系统在到达链中最后一个块的最后一个字节时设置feof。


我认为这是最接近我的问题的答案。所以当你执行 FILE *fp = fopen() 时,FILE类型是否已经知道文件的大小,可以被 feof() 使用? - Austin
通常是这样的。元信息的一部分是文件大小(还有日期、位置、以前版本等)。元信息与文件一起存储,并在打开文件时检索。但实际上,这并不是文件系统所必需的,因此它取决于它是如何实现的。 - Gregg

2

feof()如何知道文件何时结束?

当代码试图读取超过最后一个字符时。

根据文件类型,最后一个字符不一定在尝试读取它之前就已知,只有在尝试读取它并且没有字符可用时才会知道。


演示feof()从0变为1的示例代码:

#include <stdio.h>

void ftest(int n) {
  FILE *ostream = fopen("tmp.txt", "w");
  if (ostream) {
    while (n--) {
      fputc('x', ostream);
    }
    fclose(ostream);
  }
  FILE *istream = fopen("tmp.txt", "r");
  if (istream) {
    char buf[10];
    printf("feof() %d\n", feof(istream));
    printf("fread  %zu\n", fread(buf, 1, 10, istream));
    printf("feof() %d\n", feof(istream));
    printf("fread  %zu\n", fread(buf, 1, 10, istream));
    printf("feof() %d\n", feof(istream));
    puts("");
    fclose(istream);
  }
}

int main(void) {
  ftest(9);
  ftest(10);
  return 0;
}

输出

feof() 0
fread  9  // 10 character read attempted, 9 were read
feof() 1  // eof is set as previous read attempted to read passed the 9th or last char
fread  0
feof() 1

feof() 0
fread  10  // 10 character read attempted, 10 were read
feof() 0   // eof is still clear as no attempt to read passed the 10th, last char
fread  0
feof() 1

不错的代码片段。但是你的意思是最后一个字符不一定是已知的吗? - user6338533
@JohnSmithSr。是的,帖子已经修改,您“不”是正确的。 - chux - Reinstate Monica

-2

feof()函数在读取EOF字符时设置文件结束指示器。因此,当feof()读取最后一项时,EOF不会首先被读取。由于没有设置EOF指示器,feof()返回零,流再次进入while循环。这次,fgets知道下一个字符是EOF,它将其丢弃并返回NULL,但也设置了EOF指示器。因此,feof()检测到文件结束指示器并返回非零值,从而打破while循环。


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