EOF是什么?

作者: 阮一峰

日期: 2011年11月12日

珠峰培训

我学习C语言的时候,遇到的一个问题就是EOF

它是end of file的缩写,表示"文字流"(stream)的结尾。这里的"文字流",可以是文件(file),也可以是标准输入(stdin)。

比如,下面这段代码就表示,如果不是文件结尾,就把文件的内容复制到屏幕上。

  int c;

  while ((c = fgetc(fp)) != EOF) {

    putchar (c);

  }

很自然地,我就以为,每个文件的结尾处,有一个叫做EOF的特殊字符,读取到这个字符,操作系统就认为文件结束了。

但是,后来我发现,EOF不是特殊字符,而是一个定义在头文件stdio.h的常量,一般等于-1。

  #define EOF (-1)

于是,我就困惑了。

如果EOF是一个特殊字符,那么假定每个文本文件的结尾都有一个EOF(也就是-1),还是可以做到的,因为文本对应的ASCII码都是正值,不可能有负值。但是,二进制文件怎么办呢?怎么处理文件内部包含的-1呢?

这个问题让我想了很久,后来查了资料才知道,在Linux系统之中,EOF根本不是一个字符,而是当系统读取到文件结尾,所返回的一个信号值(也就是-1)。至于系统怎么知道文件的结尾,资料上说是通过比较文件的长度。

所以,处理文件可以写成下面这样:

  int c;

  while ((c = fgetc(fp)) != EOF) {

    do something

  }

这样写有一个问题。fgetc()不仅是遇到文件结尾时返回EOF,而且当发生错误时,也会返回EOF。因此,C语言又提供了feof()函数,用来保证确实是到了文件结尾。上面的代码feof()版本的写法就是:

  int c;

  while (!feof(fp)) {

    c = fgetc(fp);

    do something;

  }

但是,这样写也有问题。fgetc()读取文件的最后一个字符以后,C语言的feof()函数依然返回0,表明没有到达文件结尾;只有当fgetc()向后再读取一个字符(即越过最后一个字符),feof()才会返回一个非零值,表示到达文件结尾。

所以,按照上面这样写法,如果一个文件含有n个字符,那么while循环的内部操作会运行n+1次。所以,最保险的写法是像下面这样:

  int c = fgetc(fp);

  while (c != EOF) {

    do something;

    c = fgetc(fp);

  }

  if (feof(fp)) {

    printf("\n End of file reached.");

  } else {

    printf("\n Something went wrong.");

  }

除了表示文件结尾,EOF还可以表示标准输入的结尾。

  int c;

  while ((c = getchar()) != EOF) {

    putchar(c);

  }

但是,标准输入与文件不一样,无法事先知道输入的长度,必须手动输入一个字符,表示到达EOF。

Linux中,在新的一行的开头,按下Ctrl-D,就代表EOF(如果在一行的中间按下Ctrl-D,则表示输出"标准输入"的缓存区,所以这时必须按两次Ctrl-D);Windows中,Ctrl-Z表示EOF。(顺便提一句,Linux中按下Ctrl-Z,表示将该进程中断,在后台挂起,用fg命令可以重新切回到前台;按下Ctrl-C表示终止该进程。)

那么,如果真的想输入Ctrl-D怎么办?这时必须先按下Ctrl-V,然后就可以输入Ctrl-D,系统就不会认为这是EOF信号。Ctrl-V表示按"字面含义"解读下一个输入,要是想按"字面含义"输入Ctrl-V,连续输入两次就行了。

(完)

一灯学堂

优达学城

留言(29条)

在Linux系统之中,EOF根本不是一个字符,而是当系统读取到文件结
尾,所返回的一个信号值(也就是-1)
 int c;
  while ((c = fgetc(fp)) != EOF)
------------
有个问题请教一下,如果c = fgetc(fp) 读到的是二进制的-1,怎么区分读到的c(-1)跟信号值(-1)呢?

@wdd:

我的理解是这样的,不一定对,欢迎讨论。

根据函数定义,除了EOF,fgetc()函数返回的都是unsigned char类型,而c是int类型,也就是说,这里发生了类型转换。除了最后一个字节,c的前几个字节肯定都是0。所以,除非出现EOF,否则c肯定是一个正值,不可能等于-1。

你输入-1实际上是‘-’ ‘1’。除非你真用二进制读写函数读写文件那就不存在EOF而是按字节块读写,fgetc是文本型处理文件的,不存在读2进制的-1,一次只处理一个字节,这里有个重要概念,文件分为:文本视图,2进制视图。打开文件分文本模式打开,2进制打开。处理也分文本模式和2进制模式。所以不要混淆

补充一点,Linux和Windows的输入分别是采用非阻塞式和阻塞式,也就是说Linux中你按下Ctrl+D后,程序立即响应,将键盘输入的东西放到输入缓冲区中(同时忽略输入的这个Ctrl+D),而Windows下则是你按了回车以后它才去看你输入有没有Ctrl+Z的

对了,至于那个-1,请务必好好研读下ASC II,他是7位0~127个字符,第8位是没有用的,所以signed char中如果第8位为1,表示负数也好正数也好,在ASC表里都不对应任何字符

int
fgetc(FILE *fp)
{
return (__sgetc(fp));
}
google codesearch搜到的代码是这样的,返回值是int,不是unsigned int

引用阮一峰的发言:

根据函数定义,除了EOF,fgetc()函数返回的都是unsigned char类型,而c是int类型,也就是说,这里发生了类型转换。除了最后一个字节,c的前几个字节肯定都是0。所以,除非出现EOF,否则c肯定是一个正值,不可能等于-1。

这里的思路是对的,但说法上有问题。

在阮一峰先生的那篇推荐C语言教材的文章中,我说过,阮先生该看一些基础性的文档。C 语言中,函数的类型是由函数声明确定的,参数和返回值的类型都不能变。您看 K&R 有困惑,说明您和其它学习者交流不足,有些课堂上接触的基础概念您不熟悉。
C 语言标准库中,处理单个字符的函数一般都是将字符当作 int 型处理的,所以不存在什么类型转换,直接就可以将 EOF 和 char 返回为不冲突的二进制表达形式。

引用iSayme的发言:

google codesearch搜到的代码是这样的,返回值是int,不是unsigned int

fgetc要返回EOF,返回值的类型肯定是int啊。

我是说,除了EOF,其他返回值都是unsigned char。

引用wdd的发言:

在Linux系统之中,EOF根本不是一个字符,而是当系统读取到文件结
尾,所返回的一个信号值(也就是-1)
 int c;
  while ((c = fgetc(fp)) != EOF)
------------
有个问题请教一下,如果c = fgetc(fp) 读到的是二进制的-1,怎么区分读到的c(-1)跟信号值(-1)呢?

可以区分的,原因:32位系统下,int是4字节的,-1对应的是0xFFFFFFFF,而fgetc()是一个个字节地读,所以,如果有一个二进制文件的4个字节是-1,那么实际上会读出4个0xFF来。

引用iSayme的发言:

int
fgetc(FILE *fp)
{
return (__sgetc(fp));
}
google codesearch搜到的代码是这样的,返回值是int,不是unsigned int

这个要看编译器。。。 每个编译器规范不一样,ANSI C不规定是signed 还是 unsigned ,char本身就是用整形实现的

不错,既懂程序又懂英语,人才。

引用Dan的发言:


这个要看编译器。。。 每个编译器规范不一样,ANSI C不规定是signed 还是 unsigned ,char本身就是用整形实现的


其实 C99 里的7.19.7.1小节(n1256.pdf 的第308页)规定了 fgetc 要按照 unsigned char 来读文件中的字节,并转为 int 型返回。

虽然是一个简单的问题,但看你娓娓道来十分有趣。

应该是这样吧:
首先肯定的是返回值都是int型的。
如果读二进制读到-1,则其实读到的是0xff,fgetc返回时将其转为int型即0x00ff,对于int型来说,0x00ff不等于-1;
如果fgetc返回值是-1,则说明返回的是int型的-1即0xffff;

阮兄,您的rss在谷歌reader上总是报错啊

受教了!! 謝謝你!! ^^b

俺以前一直都是不求甚解,看到大伙讨论,man了一下fgetc:

fgetc() reads the next character from stream and returns it as an unsigned char cast to an int, or EOF on end of file or error.

确实是无符号字符的方式;

另外,fgetc只是取流的内容的接口,与流的实际存在形式是分离的,因此设定一些实现无关的标识来标明特殊情况也是合理的。

谢谢分享。一直都不太懂eof,现在廓然开朗。

fgetc() reads the next character from stream and returns it as an unsigned char cast to an int, or EOF on end of file or error.

这两点肯定是对的!

另外在K&R书中,提到一个经典的bug:

char c;
while((c = fgetc(stdin)) != EOF)
putchar(c);
......

fgetc(stdin)返回的一个unsigned char然后转换成int,然后在赋值运算:
c = fgetc(stdin)中又转换为char。最后在“!=”运算符中又要从char转换到int。
那么:(int)0xff ?= (EOF = 0xffff)呢?
如果系统的char是默认是signed char,那么没有问题。因为(int)((signed char)0xff) == (EOF = 0xffff),可以正确的表示到达了文件(输入)末尾的概念。
但如果系统的char是默认是unsigned char,那么就有问题了。因为(int)((unsigned char)0xff != (EOF = 0xffff)。这样就没有办法正确的表达文件(输入)到达末尾的概念。这样就成了一个死循环。

给出一段严重代码:

给出一段验证代码:
#include
int main()
{
signed char sc = 0xff;
unsigned char uc = 0xff;
int i = sc;
int j = uc;
int eof = -1; // END OF FILE

printf("i = %d = 0x%x\n", i, i);
printf("j = %d = 0x%x\n", j, j);
printf("sc == eof: %d\n", (sc == eof));
printf("uc == eof: %d\n", (uc == eof));

return 0;
}
运行结果:

digdeep@ubuntu:~/uulp$ ./char
i = -1 = 0xffffffff
j = 255 = 0xff
sc == eof: 1
uc == eof: 0

EOF 叫 End of File Condition,自然不是字符
还有好多神马:

tty: Ctrl+D
cmd: Ctrl+Z
std::basic_ios::eof()
std::istream_iterator(is)
...

我记得Windows CMD中得按下^z后再按Enter才生效,而且是不区分在空行首与否的,即不像Unix的行首^D那样直接flush buffer…

引用wdd的发言:
在Linux系统之中,EOF根本不是一个字符,而是当系统读取到文件结尾,所返回的一个信号值(也就是-1) int c;  while ((c = fgetc(fp)) != EOF)------------有个问题请教一下,如果c = fgetc(fp) 读到的是二进制的-1,怎么区分读到的c(-1)跟信号值(-1)呢?

-1 是1111 1111 文件里不出现这个就行了。

引用阮一峰的发言:

fgetc要返回EOF,返回值的类型肯定是int啊。

我是说,除了EOF,其他返回值都是unsigned char。

阮先生,谢谢你的分享,你的这篇博文让我回想到以前学习的一篇文章(http://faq.cprogramming.com/cgi-bin/smartfaq.cgi?answer=1048865140&id=1043284351),里面有关于EOF的解释。
我根据自己的理解、用工具观察的二进制结果加上程序代码,做拙文一篇, 希望能够回答上面的问题。
http://www.cnblogs.com/cando/archive/2012/09/17/2689070.html

那如果要把EOF()運用VB2010的程式碼打出來要運用何種方式呢?

由於我們學的是2010版因此每次看到EOF()都看不懂,然而在看到您打的文章而明白了他的含意,但是我還是不知道要運用何種方法才能把EOF()運用VB2010的方式打出來?

这个跟文件系统有关,所有文件的存储都是分块的,看一下文件的结构体就清楚了。文件处理函数会去判断文件是否结束(读取字节 == 文件大小),如果是就输出-1 (0xFFFFFFFF)

那么类比一下,在C语言中NULL 是#define NULL 0

是否意味着NULL也是一个信号值?

原来 EOF 是 end of file 的意思,学习了,感谢!

那么对于pipe这种特殊的文件,eof是怎么产生,并且判断的呢?

我要发表看法

«-必填

«-必填,不公开

«-我信任你,不会填写广告链接