feof() 如何真正知道何时到达文件末尾?

How does feof() actually know when the end of file is reached?

我是 C++ 的初学者,正在努力更好地理解 feof()。我读到 feof() 标志仅在尝试读取文件末尾后才设置为 true 很多次,如果初学者执行 while(!feof(file)) 之类的操作,他们会比他们预期的多读一次。不过,我想了解的是,它实际上如何解释已尝试读取文件末尾的内容?是否已经读入了整个文件并且已知字符数,或者是否有其他机制在起作用?

我意识到这可能是某个地方的重复问题,但我一直无法找到它,可能是因为我不知道如何最好地表达我的问题。如果已经有答案,link 将不胜感激。谢谢。

feof() 是标准 C 库 buffered I/O 的一部分。由于它是缓冲的,fread() 预读了一些数据(当然 而不是 整个文件)。如果在缓冲时,fread() 检测到 EOF(底层 OS 例程 return 是一个特殊值,通常是 -1),它会在 FILE 结构上设置一个标志. feof() 只是检查该标志。所以 feof() returning true 本质上意味着“先前的读取尝试遇到文件末尾”。

如何 检测到 EOF 是 OS/FS-specific 并且与 C library/language 无关。 OS 有一些接口可以从文件中读取数据。 C 库只是 OS 和程序之间的桥梁,因此如果您转到另一个 OS,则不必更改您的程序。 OS 知道文件是如何存储在它的文件系统中的,所以它知道如何检测 EOF。我的猜测是,通常它是通过将当前位置与文件的长度进行比较来执行的,但这可能并不那么容易,并且可能涉及很多低级细节(例如,如果文件在网络驱动器上怎么办?)。

一个有趣的问题是,当流结束时会发生什么,但尚未被任何读取检测到。例如,如果您打开一个空文件。在 fread() return 之前第一次调用 feof() 是对还是错?答案很可能是错误的。 The docs 对这个问题不是很清楚:

This indicator is generally set by a previous operation on the stream that attempted to read at or past the end-of-file.

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

大多数文件系统都维护有关文件的元信息(包括它的大小),并且尝试读取超过结尾的结果会导致设置 feof 标志。其他的,例如,旧的或轻量级的文件系统,在到达链中最后一个块的最后一个字节时设置 feof。

无论 C++ 库做什么,最终它都必须从文件中读取。在操作系统的某个地方,有一段代码最终会处理该读取。它从文件系统获取文件的长度,存储方式与文件系统存储其他所有内容的方式相同。知道了文件的长度,读取的位置,读取的字节数,就可以做出低级读取命中文件末尾的判断。

做出决定后,它会向上传递到堆栈中。最终,它到达标准库,该库在内部记录已到达文件末尾。当对库的读取请求试图越过记录的结尾时,设置 EOF 标志并且 feof 将开始返回 true。

How does feof() actually know when the end of file is reached?

当代码尝试读取时通过了最后一个字符。

根据文件类型,最后一个字符不一定是已知的,直到尝试读取它并且没有可用字符为止。


示例代码演示 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

feof() 函数在读取 EOF 字符时设置文件结束指示符。所以当 feof() 读取最后一项时,EOF 一开始并没有被一起读取。由于没有设置 EOF 指示符并且 feof() returns 为零,因此流程再次进入 while 循环。这次 fgets 知道下一个字符是 EOF,它丢弃它并且 returns NULL 但也设置了 EOF 指示符。因此 feof() 检测到文件结尾指示符和 returns 一个非零值,因此打破了 while 循环。