猿问

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

我看到人们最近在很多帖子中试图读取这样的文件。

#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;
    }}

这个while( !feof(fp))循环有什么问题?


墨色风雨
浏览 1413回答 5
5回答

MMMHUHU

我想提供一个抽象的,高层次的观点。并发性和同时性I / O操作与环境交互。环境不是您的计划的一部分,也不在您的控制之下。环境真正与您的程序“同时”存在。与所有并发的事情一样,关于“当前状态”的问题没有意义:并发事件中没有“同时性”的概念。国家的许多属性根本不同时存在。让我更准确地说:假设你想问,“你有更多的数据吗?”&nbsp;您可以询问并发容器或I / O系统。但答案通常是不可行的,因而毫无意义。那么如果容器说“是” - 当你尝试阅读时,它可能不再有数据。同样,如果答案为“否”,则在您尝试阅读时,数据可能已到达。结论是,目前根本是没有像“我有数据”这样的属性,因为你无法对任何可能的答案做出有意义的回应。(缓冲输入的情况略好一些,你可以想象得到一个“是的,我有数据”构成某种保证,但你仍然必须能够处理相反的情况。并且输出情况肯定和我描述的一样糟糕:你永远不知道那个磁盘或那个网络缓冲区是否已满。)因此,我们得出结论,这是不可能的,而事实上未合理的,要问的I / O系统是否将能够执行I / O操作。我们可以与它交互的唯一可能方式(就像并发容器一样)是尝试操作并检查它是成功还是失败。在您与环境交互的那一刻,然后才能知道交互是否真的可能,并且此时您必须承诺执行交互。(如果你愿意的话,这是一个“同步点”。)EOF现在我们到了EOF。EOF是您尝试的&nbsp;I / O操作获得的响应。这意味着您正在尝试读取或写入某些内容,但在执行此操作时,您无法读取或写入任何数据,而是遇到了输入或输出的结尾。对于基本上所有的I / O API都是如此,无论是C标准库,C ++ iostream还是其他库。只要I / O操作成功,您就无法知道未来的操作是否会成功。您必须首先尝试操作然后响应成功或失败。例子在每个示例中,请注意我们首先尝试I / O操作,然后在结果有效时使用结果。进一步注意,我们总是必须使用I / O操作的结果,尽管结果在每个示例中采用不同的形状和形式。C stdio,从文件中读取:for&nbsp;(;;)&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;size_t&nbsp;n&nbsp;=&nbsp;fread(buf,&nbsp;1,&nbsp;bufsize,&nbsp;infile); &nbsp;&nbsp;&nbsp;&nbsp;consume(buf,&nbsp;n); &nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(n&nbsp;<&nbsp;bufsize)&nbsp;{&nbsp;break;&nbsp;}}我们必须使用的结果是n,读取的元素数量(可能只有零)。C STDIO,&nbsp;scanf:for&nbsp;(int&nbsp;a,&nbsp;b,&nbsp;c;&nbsp;scanf("%d&nbsp;%d&nbsp;%d",&nbsp;&a,&nbsp;&b,&nbsp;&c)&nbsp;==&nbsp;3;&nbsp;)&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;consume(a,&nbsp;b,&nbsp;c);}我们必须使用的结果是scanf转换的元素的返回值。C ++,iostreams格式化提取:for&nbsp;(int&nbsp;n;&nbsp;std::cin&nbsp;>>&nbsp;n;&nbsp;)&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;consume(n);}我们必须使用的结果std::cin本身,可以在布尔上下文中进行评估,并告诉我们流是否仍然处于good()状态。C ++,iostreams getline:for&nbsp;(std::string&nbsp;line;&nbsp;std::getline(std::cin,&nbsp;line);&nbsp;)&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;consume(line);}我们必须再次使用的结果std::cin,就像以前一样。POSIX,write(2)刷新缓冲区:char&nbsp;const&nbsp;*&nbsp;p&nbsp;=&nbsp;buf;ssize_t&nbsp;n&nbsp;=&nbsp;bufsize;for&nbsp;(ssize_t&nbsp;k&nbsp;=&nbsp;bufsize;&nbsp;(k&nbsp;=&nbsp;write(fd,&nbsp;p,&nbsp;n))&nbsp;>&nbsp;0;&nbsp;p&nbsp;+=&nbsp;k,&nbsp;n&nbsp;-=&nbsp;k)&nbsp;{}if&nbsp;(n&nbsp;!=&nbsp;0)&nbsp;{&nbsp;/*&nbsp;error,&nbsp;failed&nbsp;to&nbsp;write&nbsp;complete&nbsp;buffer&nbsp;*/&nbsp;}我们在这里使用的结果是k,写入的字节数。这里的要点是我们只能知道写操作后写入了多少字节。POSIX&nbsp;getline()char&nbsp;*buffer&nbsp;=&nbsp;NULL;size_t&nbsp;bufsiz&nbsp;=&nbsp;0;ssize_t&nbsp;nbytes;while&nbsp;((nbytes&nbsp;=&nbsp;getline(&buffer,&nbsp;&bufsiz,&nbsp;fp))&nbsp;!=&nbsp;-1){ &nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;Use&nbsp;nbytes&nbsp;of&nbsp;data&nbsp;in&nbsp;buffer&nbsp;*/}free(buffer);我们必须使用的结果是nbytes,直到并包括换行符的字节数(如果文件没有以换行符结尾,则为EOF)。请注意,-1当发生错误或达到EOF时,函数显式返回(而不是EOF!)。您可能会注意到我们很少拼出实际的单词“EOF”。我们通常以某种其他方式检测错误条件,这对我们来说更加有趣(例如,无法执行我们期望的I / O)。在每个示例中都有一些API功能可以明确告诉我们已经遇到EOF状态,但事实上这并不是一个非常有用的信息。它比我们经常关心的更详细。重要的是I / O是否成功,比失败更重要。实际查询EOF状态的最后一个示例:假设您有一个字符串并且想要测试它是否完整地表示整数,除了空格之外没有额外的位。使用C ++ iostream,它是这样的:std::string&nbsp;input&nbsp;=&nbsp;"&nbsp;&nbsp;&nbsp;123&nbsp;&nbsp;&nbsp;";&nbsp;&nbsp;&nbsp;//&nbsp;examplestd::istringstream&nbsp;iss(input);int&nbsp;value;if&nbsp;(iss&nbsp;>>&nbsp;value&nbsp;>>&nbsp;std::ws&nbsp;&&&nbsp;iss.get()&nbsp;==&nbsp;EOF)&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;consume(value);}&nbsp;else&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;error,&nbsp;"input"&nbsp;is&nbsp;not&nbsp;parsable&nbsp;as&nbsp;an&nbsp;integer}我们在这里使用两个结果。第一个是iss流对象本身,用于检查格式化的提取是否value成功。但是,在消耗空白之后,我们执行另一个I / O /操作,iss.get()并期望它作为EOF失败,如果整个字符串已经被格式化提取消耗,则会出现这种情况。在C标准库中,您可以strto*l通过检查结束指针是否已到达输入字符串的末尾来实现与函数类似的操作。答案while(!eof)这是错误的,因为它测试的东西是无关紧要的,无法测试你需要知道的东西。结果是您错误地执行了代码,该代码假定它正在访问已成功读取的数据,而事实上这种情况从未发生过。

12345678_0001

不,这并不总是错的。如果您的循环条件是“我们还没有尝试读取文件末尾”,那么您可以使用while (!feof(f))。然而,这不是一个常见的循环条件 - 通常你想测试其他东西(例如“我可以阅读更多”)。while (!feof(f))没错,它只是用错了。

交互式爱情

feof()表示是否曾尝试读取文件末尾。这意味着它几乎没有预测效果:如果它是真的,你确定下一个输入操作将失败(你不确定前一个输入操作是否失败),但如果它是假的,你不确定下一个输入操作会成功。此外,输入操作可能由于文件结尾之外的其他原因而失败(格式化输入的格式错误,纯IO故障 - 磁盘故障,网络超时 - 适用于所有输入类型),因此即使您可以预测文件的结尾(任何尝试实现Ada one的人,如果你需要跳过空格,并且它对交互式设备有不良影响,它会告诉你它可能很复杂 - 有时会强迫输入下一个在开始处理上一个之前的行),因此,C语中的正确习惯是将IO操作成功循环作为循环条件,然后测试失败的原因。例如:while&nbsp;(fgets(line,&nbsp;sizeof(line),&nbsp;file))&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;note&nbsp;that&nbsp;fgets&nbsp;don't&nbsp;strip&nbsp;the&nbsp;terminating&nbsp;\n,&nbsp;checking&nbsp;its &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;presence&nbsp;allow&nbsp;to&nbsp;handle&nbsp;lines&nbsp;longer&nbsp;that&nbsp;sizeof(line),&nbsp;not&nbsp;showed&nbsp;here&nbsp;*/ &nbsp;&nbsp;&nbsp;&nbsp;...}if&nbsp;(ferror(file))&nbsp;{ &nbsp;&nbsp;&nbsp;/*&nbsp;IO&nbsp;failure&nbsp;*/}&nbsp;else&nbsp;if&nbsp;(feof(file))&nbsp;{ &nbsp;&nbsp;&nbsp;/*&nbsp;format&nbsp;error&nbsp;(not&nbsp;possible&nbsp;with&nbsp;fgets,&nbsp;but&nbsp;would&nbsp;be&nbsp;with&nbsp;fscanf)&nbsp;or&nbsp;end&nbsp;of&nbsp;file&nbsp;*/}&nbsp;else&nbsp;{ &nbsp;&nbsp;&nbsp;/*&nbsp;format&nbsp;error&nbsp;(not&nbsp;possible&nbsp;with&nbsp;fgets,&nbsp;but&nbsp;would&nbsp;be&nbsp;with&nbsp;fscanf)&nbsp;*/}
随时随地看视频慕课网APP
我要回答