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,连续输入两次就行了。

(完)

留言(30条)

在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是怎么产生,并且判断的呢?

引用wdd的发言:

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

只要仔细看这个函数的说明就不会有这个问题了。

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。

"unsigned char cast to an int",unsigned char 转换成的 int 是不会出现 负数 的,所以 EOF 可以和文件里面的字符区别开来。

我要发表看法

«-必填

«-必填,不公开

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