为什么是“;而(!feof(file))”;总是错吗?

为什么是“;而(!feof(file))”;总是错吗?,c,file,while-loop,eof,feof,C,File,While Loop,Eof,Feof,使用feof()控制读取循环有什么问题?例如: #include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { char *path = "stdin"; FILE *fp = argc > 1 ? fopen(path=argv[1], "r") : stdin; if( fp == NULL ){

使用
feof()
控制读取循环有什么问题?例如:

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

int
main(int argc, char **argv)
{
    char *path = "stdin";
    FILE *fp = argc > 1 ? fopen(path=argv[1], "r") : stdin;

    if( fp == NULL ){
        perror(path);
        return EXIT_FAILURE;
    }

    while( !feof(fp) ){  /* THIS IS WRONG */
        /* Read and process data from file… */
    }
    if( fclose(fp) != 0 ){
        perror(path);
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}
#包括
#包括
int
主(内部argc,字符**argv)
{
char*path=“stdin”;
文件*fp=argc>1?fopen(路径=argv[1],“r”):stdin;
如果(fp==NULL){
佩罗尔(路径);
返回退出失败;
}
而(!feof(fp)){/*这是错误的*/
/*从文件中读取和处理数据*/
}
如果(fclose(fp)!=0){
佩罗尔(路径);
返回退出失败;
}
返回退出成功;
}

这个循环怎么了?

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

它错了,因为(在没有读取错误的情况下)它进入循环的次数比作者预期的要多一次。如果出现读取错误,循环永远不会终止

考虑以下代码:

/* WARNING: demonstration of bad coding technique!! */

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

FILE *Fopen(const char *path, const char *mode);

int main(int argc, char **argv)
{
    FILE *in;
    unsigned count;

    in = argc > 1 ? Fopen(argv[1], "r") : stdin;
    count = 0;

    /* WARNING: this is a bug */
    while( !feof(in) ) {  /* This is WRONG! */
        fgetc(in);
        count++;
    }
    printf("Number of characters read: %u\n", count);
    return EXIT_SUCCESS;
}

FILE * Fopen(const char *path, const char *mode)
{
    FILE *f = fopen(path, mode);
    if( f == NULL ) {
        perror(path);
        exit(EXIT_FAILURE);
    }
    return f;
}
/*警告:演示错误的编码技术*/
#包括
#包括
文件*Fopen(常量字符*路径,常量字符*模式);
int main(int argc,字符**argv)
{
文件*in;
无符号计数;
in=argc>1?Fopen(argv[1],“r”):stdin;
计数=0;
/*警告:这是一个错误*/
而(!feof(in)){/*这是错误的*/
fgetc(in);
计数++;
}
printf(“读取的字符数:%u\n”,计数);
返回退出成功;
}
文件*Fopen(常量字符*路径,常量字符*模式)
{
文件*f=fopen(路径、模式);
如果(f==NULL){
佩罗尔(路径);
退出(退出失败);
}
返回f;
}
此程序将始终打印一个大于输入流中字符数的字符(假设没有读取错误)。考虑输入流为空的情况:

$ ./a.out < /dev/null
Number of characters read: 1
$./a.out
在本例中,
feof()
在读取任何数据之前被调用,因此它返回false。进入循环后,调用
fgetc()
(并返回
EOF
),计数递增。然后调用
feof()
,并返回true,导致循环中止

在所有这些情况下都会发生这种情况
feof()
直到流上的读取遇到文件结尾后才返回true。
feof()
的目的不是检查下一次读取是否会到达文件末尾。
feof()
的目的是确定先前读取函数的状态 并区分错误条件和数据流的结尾。如果
fread()
返回0,则必须使用
feof
/
ferror
来确定是否发生错误或是否消耗了所有数据。类似地,如果
fgetc
返回
EOF
feof()
仅在fread返回零或
fgetc
返回
EOF
后才有用。在此之前,
feof()
将始终返回0

调用
feof()
之前,始终需要检查读取的返回值(要么是
fread()
,要么是
fscanf()
,要么是
fgetc()

更糟的是,考虑出现读取错误的情况。在这种情况下,

fgetc()
返回
EOF
feof()
返回false,循环永远不会终止。在所有使用(!feof(p))
的情况下,必须至少在循环内部检查
ferror()
,或者至少应将while条件替换为
while(!feof(p)和&!ferror(p))
,或者存在无限循环的可能性,在处理无效数据时,可能会喷出各种垃圾

因此,总而言之,尽管我不能肯定地说,在任何情况下,当(!feof(f))时,写“
在语义上都是正确的”(尽管必须在循环内部进行另一次带中断的检查,以避免读错误时出现无限循环),在这种情况下,几乎可以肯定它总是错的。即使出现了一个正确的情况,它的习惯用法也是错误的,因此它不是编写代码的正确方法。任何看到该代码的人都应该立即犹豫并说,“这是一个bug”。还可能打作者一巴掌(除非作者是你的老板,在这种情况下,建议谨慎处理。)

feof()
表示是否试图读取文件末尾。这意味着它几乎没有预测效果:如果它是真的,你肯定下一个输入操作会失败(顺便说一句,你不确定上一个操作会失败),但是如果它是假的,你不确定下一个输入操作会成功。此外,输入操作可能会由于文件结尾以外的其他原因而失败(格式化输入的格式错误,所有输入类型的纯IO故障——磁盘故障、网络超时),因此即使您可以预测文件结尾(任何试图实现Ada one的人都会告诉你,如果你需要跳过空格,它可能会很复杂,并且会对交互设备产生不良影响——有时会在开始处理前一行之前强制输入下一行),你必须能够处理故障

因此,C中正确的习惯用法是以IO操作成功作为循环条件进行循环,然后测试失败的原因。例如:

while (fgets(line, sizeof(line), file)) {
    /* note that fgets don't strip the terminating \n, checking its
       presence allow to handle lines longer that sizeof(line), not showed here */
    ...
}
if (ferror(file)) {
   /* IO failure */
} else if (feof(file)) {
   /* format error (not possible with fgets, but would be with fscanf) or end of file */
} else {
   /* format error (not possible with fgets, but would be with fscanf) */
}

我想提供一个抽象的、高层次的视角

并发性和同时性 I/O操作与环境交互。环境不是您程序的一部分,也不在您的控制之下。环境确实与您的程序“并发”存在。与所有并发的事物一样,questi
  for (;;) {
      size_t n = fread(buf, 1, bufsize, infile);
      consume(buf, n);
      if (n == 0) { break; }
  }
  for (int a, b, c; scanf("%d %d %d", &a, &b, &c) == 3; ) {
      consume(a, b, c);
  }
  for (int n; std::cin >> n; ) {
      consume(n);
  }
  for (std::string line; std::getline(std::cin, line); ) {
      consume(line);
  }
  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 */ }
  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);
  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
  }
#include <stdio.h>

int main(int argc, char *argv[])
{
  FILE *in = fopen("testfile.txt", "r");

  while(1) {
    char c = fgetc(in);
    if (feof(in)) break;
    printf("%c", c);
  }

  fclose(in);
  return 0;
}
#include <stdio.h>

int main(int argc, char *argv[])
{
  FILE *in = fopen("testfile.txt", "r");

  while(!feof(in)) {
    printf("%c", fgetc(in));
  }

  fclose(in);
  return 0;
}