gpt4 book ai didi

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

转载 作者:太空宇宙 更新时间:2023-11-04 04:10:41 25 4
gpt4 key购买 nike

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

#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++ IOFROTS或其他库。只要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/57999139/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com