EOF 和 feof()

查看 stdio.h 可以看到如下定义:

#define  EOF  (-1)

#define  _IOEOF  0x0010 
#define  feof(_stream)  ((_stream)->_flag & _IOEOF)

由此可以看出,这两种方式的原理是不同的。

在这里先说下EOF和feof()这个两个宏定义,在我们学的课本中有这样的描述。

EOF是不可输出字符,因此不能在屏幕上显示。由于字符的ASCII码不可能出现-1,因此EOF定义为-1是合适的。当读入的字符值等于EOF 时,表示读入的已不是正常的字符而是文件结束符,但这适用对文本文件的读写。在二进制文件中,信息都是以数值方式存在的。EOF的值可能就是所要处理的二进制文件中的信息。这就出现了需要读入有用数据却被处理为“文件结束“的情况。为了解决这个问题,C提供了一个feof()函数,可以用它来判断文件是否结束。feof(fp)用于测试fp所指向的文件的当前状态是否为“文件结束”。如果是,函数则返回的值是1(真),否则为0(假)。

说了这两个的定义,肯定还对二进制文件和文本文件的区别有些模糊(唉,因为我当时就对这些搞不懂),那现在就回顾下这两个文件的概念。C语言支持的是流式文件,它把文件看作由一个一个的字符(字节)数据组成的序列。根据数据的组织和操作形式,可以分为ASCII文件和二进制文件。

ASCII文件又称为文本文件,它是在一个字节的存储单元上存放一个字符(在外存中存放的是该字符的ASCII码,每个字符将占一个字节)。

二进制文件是把内存中的数据按其在内存中的存储格式在磁盘上原样保存。 

对字符而言,由于其外存存储格式和内存表示格式相同,所以,在外存上也存放每个字符的ASCII码。
但是说EOF只能用于文本文件,其实不然,这点不是特别的准确,还要看定义的变量的类型。

下面这段程序对文本文件和二进制文件都可以:
int c;
while((c=fgetc(fp)) != EOF)
{
printf("%X/n", c); 
}
如果读到了FF,由于c定义为int型,所以实际上c=0x000000FF,不等于EOF(-1=0xFFFFFFFF),因此不会误判为文件结尾。

但是如果把c定义为char类型,就有可能产生混淆了。
char c;
while((c=fgetc(fp)) != EOF)
{
printf("%X/n", c); 
}
因为文本文件中存储的是ASCII码,而ASCII码中FF代表空值(blank),一般不使用,所以如果读文件返回了FF,说明已经到了文本文件的结尾。但是如果是二进制文件,其中可能会包含FF,因此不能把读到EOF作为文件结束的条件,此时只能用feof()函数。

在VC里,只有当文件位置指针(fp->_ptr)到了文件末尾,然后再发生读/写操作时,标志位(fp->_flag)才会被置为含有_IOEOF。然后再调用feof(),才会得到文件结束的信息。

因此,如果运行如下程序:
char c;
while(!feof(fp))
{
c = fgetc(fp);
printf("%X/n", c); 
}
会发现多输出了一个FF,原因就是在读完最后一个字符后,fp->flag仍然没有被置为_IOEOF,因而feof()仍然没有探测到文件结尾。直到再次调用fgetc()执行读操作,feof()才能探测到文件结尾。这样就多输出了一个-1(即FF)。

 

/* feof example: byte counter */
#include <stdio.h>

int main ()
{
  FILE * pFile;
  int n = 0;
  char a;
  pFile = fopen ("myfile.txt","rb");
  if (pFile==NULL) perror ("Error opening file");
  else
  {
    while ((a=fgetc(pFile) )!= EOF) {
      ++n;
      printf("%X\n",a);
    }
    
    if (feof(pFile)) {
      puts ("1  End-of-File reached.");
      printf ("Total number of bytes read: %d\n", n);
    }
    else puts ("End-of-File was not reached.");

    n = 0;
    rewind(pFile);
    //a = fgetc(pFile);
    while(!feof(pFile)){
        ++n;
        a = fgetc(pFile);
        printf("%X\n",a);
    }

    if (feof(pFile)) {
      puts ("2  End-of-File reached.");
      printf ("Total number of bytes read: %d\n", n);
    }
    else puts ("End-of-File was not reached.");

    fclose (pFile);
  }
  return 0;
}
 

 

正确的写法应该是:
char c;
c = fgetc(fp);
while(!feof(fp))
{

printf("%X/n", c); 
c = fgetc(fp);

/* 读完最后一个后,feof()没有判断到文件尾,在执行了一次fgetc,

   此时,feof()判断到了文件尾,不在进while循环,所以printf没被执行。

 */

 

看到tjyl专家顾问的帖子《C语言中文件读取的一个小技巧 》(http://emuch.net/bbs/viewthread.php?tid=1990840),里面仍然在用feof()。

今天,就谈谈这个feof()咯。

很多程序在读取文本文件时,经常会出现一个有趣的现象:程序会把文件的最后一行读两遍。因此很多软件都要求文本文件要在最后添加一个空行。造成这种尴尬的原因主要在于编程时对于如何判断文件末尾产生了误解。

以C语言为例,很多教材、参考书等都给出类似下面的代码来解释如何判断文件结束:
CODE:  [Copy to clipboard]

        /* fp是已经成功打开的可读文件指针        */
        while (!feof (fp))
        {
                /* 调用fgets()或fscanf()等函数读取文件内容        */
                /* 可以用printf()输出读取的内容,看看是否正确        */
        }
这段代码的错误在于误解了文件末尾(End-of-file,EOF)的含义和feof()函数的功能。所谓文件末尾,往往被理解为文件的最后一个字符是EOF,即–1,而feof()读到这个EOF就返回一个非0值,表示文件读完了。

然而,这是完全错误的!

不论是文本还是二进制文件,其末尾并不存在这么一个EOF字符;
feof()本身也并不具备“读”的功能,它返回0还是非0,依据的是最近一次的读取操作,如fgets()、fscanf()或fread()等函数,是否造成“错误(Error)”,因此feof()永远不可能“读”到一个EOF。


判断文件结束的正确机制应该是这样的:那些具有读取功能的函数,在读入文件的最后一个字节(Last Byte)之后,如果不再继续读,那么程序无从判断文件是否结束;只有在读入最后一个字节之后再继续试图读取时,管理磁盘文件的操作系统才会设置一个非0的错误码(Error Code)。而ferror()、feof()等函数是依据这个错误码来判断文件操作方面出现了什么错误以及文件是否结束。无论是文件操作、内存分配还是网络通信,这种判定错误的机制是非常普遍的。

因此,按照上面的代码,当fgets()或fscanf()读到文本文件的最后一行时,并没有造成错误;此时回到循环判断语句,feof()自然返回0,继续执行读取,而这次读取才会触发错误,但fgets()或fscanf()上次所读取的变量并未被更新,因此最后一行的内容会再次被输出;然后,回到循环判断,feof()这时才返回非0,退出循环。至于那种要求文本文件末尾加一个空行的做法,只不过是把空行读了两遍而已。

其实,当fgets()返回NULL或fscanf()返回EOF时,都表示读取出错;在这种情况下,才需要用feof()或ferror()指示究竟是什么错误。所以,正确的文件读取循环应该是:
CODE:  [Copy to clipboard]

        /* fp是已经成功打开的可读文件指针        */
        while (fgets(…))        
        /* 或 while (fscanf(fp, …)!=EOF)*/
        {
                …
        }
        /* printf(“%d %s\n”, feof(fp), ferror(fp));         输出错误情况        */
虽然上面讨论的是文本文件,对于二进制文件来说道理是一样的。至于带有DTD的标记语言,基本上都是调用剖析程序进行;而数据库相关的操作,基本上都是通过数据库接口函数进行的,这里就不再介绍了。

[ Last edited by yalefield on 2010-4-24 at 10:31 ]

学习了,写的非常好的。
对于fgets,gets在出错和到达文件末尾都是返回NULL,
而scanf,fscanf,sscanf在出错和到达文件尾都是返回EOF,用feof只是为了知道到底是不是因为到了文件末尾.
FILE其实是一个结构体,里面包含里文件描述符、缓冲区指针、缓冲区长度、缓冲区内当前字符数和出错标准等等。
feof的实现有的是用宏,其实就是去检查FILE结构里的_flags字段。
================================
struct _IO_FILE {
  int _flags;           /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

  /* The following pointers correspond to the C++ streambuf protocol. */
  /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
  char* _IO_read_ptr;   /* Current read pointer */
  char* _IO_read_end;   /* End of get area. */
  char* _IO_read_base;  /* Start of putback+get area. */
  char* _IO_write_base; /* Start of put area. */
  char* _IO_write_ptr;  /* Current put pointer. */
  char* _IO_write_end;  /* End of put area. */
  char* _IO_buf_base;   /* Start of reserve area. */
  char* _IO_buf_end;    /* End of reserve area. */
  /* The following fields are used to support backing up and undo. */
  char *_IO_save_base; /* Pointer to start of non-current get area. */
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */
  char *_IO_save_end; /* Pointer to end of non-current get area. */

  struct _IO_marker *_markers;

  struct _IO_FILE *_chain;

  int _fileno;
  int _blksize;
  _IO_off_t _old_offset; /* This used to be _offset but it's too small.  */

#define __HAVE_COLUMN /* temporary */
  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];

  /*  char* _save_gptr;  char* _save_egptr; */

  _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

==================================
#define __sfeof(p)      (((p)->_flags & __SEOF) != 0)
#define __sferror(p)    (((p)->_flags & __SERR) != 0)
#define __sclearerr(p)  ((void)((p)->_flags &= ~(__SERR|__SEOF)))
#define __sfileno(p)    ((p)->_file)

#ifndef lint
#ifndef _REENTRANT
#define feof(p)         __sfeof(p)
#define ferror(p)       __sferror(p)
#define clearerr(p)     __sclearerr(p)
没有摘抄完
========================

在同时读进去再写出来的时候
其实这样就可以了

fscanf(....)  or fgets(...)
while ( ! feof( fp ) ) {

printf(...) or fputs....

fscanf(....)  or fgets(...)

}
 

 

posted @ 2013-06-23 15:08  ~风~  阅读(776)  评论(0编辑  收藏  举报