c - 为什么“while(!feof(file))”总是错误的?

标签 c file while-loop feof

我最近在很多帖子中看到有人试图阅读这样的文件。
代码

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
    char *path = argc > 1 ? argv[1] : "input.txt";

    FILE *fp = fopen(path, "r");
    if( fp == NULL ) {
        perror(path);
        return EXIT_FAILURE;
    }

    while( !feof(fp) ) {  /* THIS IS WRONG */
        /* Read and process data from file… */
    }
    if( fclose(fp) == 0 ) {
        return EXIT_SUCCESS;
    } else {
        perror(path);
        return EXIT_FAILURE;
    }
}

这个回路怎么了?

最佳答案

我想提供一个抽象的,高层次的视角。
并发性和同时性
I/O操作与环境交互。环境不是程序的一部分,也不在您的控制之下。环境真的和你的程序同时存在。与所有并发的事物一样,关于“当前状态”的问题没有意义:在并发事件中没有“同时”的概念。许多状态属性根本不同时存在。
让我更精确地说:假设你想问,“你有更多的数据吗?”您可以询问并发容器或I/O系统。但答案通常是不可操作的,因此毫无意义。所以,如果容器说“是”—当您尝试阅读时,它可能不再有数据了。同样,如果答案是“否”,那么在您尝试阅读时,数据可能已经到达。结论是根本就没有“我有数据”这样的属性,因为你不能对任何可能的答案做出有意义的反应。(缓冲输入的情况稍微好一点,在缓冲输入中,您可能会得到一个“是的,我有数据”作为某种保证,但是您仍然必须能够处理相反的情况。有了输出,情况肯定和我描述的一样糟糕:您永远不知道磁盘或网络缓冲区是否已满。)
因此,我们得出结论,询问i/o系统是否能够执行i/o操作是不可能的,而且实际上是不合理的。我们可以与它交互(就像与并发容器交互)的唯一可能方法是尝试操作并检查它是否成功或失败。在您与环境交互的那一刻,然后并且只有在那时,您才能知道交互是否实际可行,并且在那一刻,您必须承诺执行交互。(如果愿意,这是一个“同步点”。)
EOF公司
现在我们到了EOF。EOF是从尝试的I/O操作获得的响应。这意味着你试图读或写一些东西,但当这样做时,你没有读或写任何数据,而是遇到了输入或输出的结尾。基本上所有的I/O API都是这样的,不管它是C标准库、C++库还是其他库。只要I/O操作成功,您就无法知道将来的操作是否会成功。你必须首先尝试这个操作,然后对成功或失败做出反应。
实例
在每个示例中,请注意,我们首先尝试I/O操作,然后在结果有效时使用它。请进一步注意,我们始终必须使用I/O操作的结果,尽管每个示例中的结果具有不同的形状和形式。
C stdio,从文件中读取:

for (;;) {
    size_t n = fread(buf, 1, bufsize, infile);
    consume(buf, n);
    if (n < bufsize) { break; }
}

我们必须使用的结果是n,即读取的元素数(可能只有零)。
标准,scanf
for (int a, b, c; scanf("%d %d %d", &a, &b, &c) == 3; ) {
    consume(a, b, c);
}

我们必须使用的结果是返回值scanf,即转换的元素数。
C++,IOFFATH格式抽取:
for (int n; std::cin >> n; ) {
    consume(n);
}

我们必须使用的结果是std::cin本身,它可以在布尔上下文中计算,并告诉我们流是否仍处于good()状态。
C++流,iGielsGETLY:
for (std::string line; std::getline(std::cin, line); ) {
    consume(line);
}

我们必须使用的结果是再次std::cin,就像以前一样。
posix,write(2)刷新缓冲区:
char const * p = buf;
ssize_t n = bufsize;
for (ssize_t k = bufsize; (k = write(fd, p, n)) > 0; p += k, n -= k) {}
if (n != 0) { /* error, failed to write complete buffer */ }

我们在这里使用的结果是k,即写入的字节数。这里的要点是,我们只能知道在写操作之后写入了多少字节。
波塞克斯getline()
char *buffer = NULL;
size_t bufsiz = 0;
ssize_t nbytes;
while ((nbytes = getline(&buffer, &bufsiz, fp)) != -1)
{
    /* Use nbytes of data in buffer */
}
free(buffer);

我们必须使用的结果是nbytes,包括换行符的字节数(如果文件没有以换行符结尾,则为EOF)。
注意,函数显式返回-1(而不是eof!)当错误发生或达到EOF时。
你可能会注意到我们很少拼出“eof”这个词。我们通常以其他更直接有趣的方式检测错误情况(例如未能执行所需的I/O)。在每个例子中,都有一些api特性可以明确地告诉我们已经遇到了eof状态,但实际上这并不是非常有用的信息。这是一个比我们通常关心的更多的细节。重要的是I/O是否成功,而不是如何失败。
最后一个实际查询eof状态的示例:假设您有一个字符串,并希望测试它是否完整地表示一个整数,除了空格之外,末尾没有多余的位。使用C++ IoSokes,它是这样的:
std::string input = "   123   ";   // example

std::istringstream iss(input);
int value;
if (iss >> value >> std::ws && iss.get() == EOF) {
    consume(value);
} else {
    // error, "input" is not parsable as an integer
}

这里我们使用两个结果。第一个是流对象本身iss,检查格式化的提取是否成功。但是,在同时使用空白之后,我们执行另一个i/o/操作value,并期望它作为eof失败,如果格式化提取已经使用了整个字符串,则会出现这种情况。
在c标准库中,通过检查结束指针是否已到达输入字符串的结尾,可以使用iss.get()函数实现类似的功能。
答案
strto*l是错误的,因为它会测试一些不相关的东西,而无法测试您需要知道的东西。结果是,您错误地执行了假定它正在访问已成功读取的数据的代码,而事实上这从未发生过。

关于c - 为什么“while(!feof(file))”总是错误的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58420125/

相关文章:

c - 为什么我的 C 程序中的浮点值会发生变化?

java - 使用 XWPFDocument 将文本附加到现有 Word 文件

python - 为什么只有最里面的嵌套 while 循环起作用?

python 重复程序而为真

c - 如何分配具有连续内存的二维数组?我如何使用它来访问行和列?举个例子

c - 为什么 scanf 函数会自动获取先前的 '\n' 值以及如何逃脱此事件?

ios - 按位右移 >> 在 Objective-C 中

c - 即使用 "rb+"打开文件也不是用二进制写的

java - JSP 项目结构 - 文件放置

java - 将值添加到列表并转换为 BigInteger - Java