代码改变世界

EOF

2012-09-17 18:21  康杜  阅读(2708)  评论(8编辑  收藏  举报

在阮一峰的网络日志里,阮先生写了一篇关于EOF的文章(http://www.ruanyifeng.com/blog/2011/11/eof.html);该文描述了EOF不是文件的结束符,而是fgetc函数读取文件,到达文件结尾的时候返回一个标志。 在宏定义里,EOF=-1。 Q: 于是有网友问如果文件中有个-1怎么办?

回想到以前学习的一篇文章(http://faq.cprogramming.com/cgi-bin/smartfaq.cgi?answer=1048865140&id=1043284351),里面有关于EOF的解释,于是我根据自己的理解、用工具观察的二进制结果加上程序代码,做拙文一篇, 希望能够回答上面的问题。

首先看看fgetc的说明:

在linux控制台中,执行命令
man 3 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()每次读取一个unsigned char字符的字符,然后转型为int型返回,如果到达文件的结束或者出错就返回EOF”.

思考:

既然是读取一个unsigned char类型的字符,为什么不返回unsigned char而是int呢?我们知道一个int就是32个bit,而一个char就是8个bit,由于int存储空间比char大,所以一个unsigned char转型为int不可能有负数的情况出现。于是fgetc函数返回int,使得可以用一个负数来表示 EOF 或其它情况。注意:一个unsigned char不可能转型为负数的int值,并不是说signed char也不是转型为负数的int值。请看如下代码

#include <stdio.h>

int main(void){
int i = -1;
signed char sc = 0xff;
unsigned char usc = 0xff;

printf("转成16进制后i是 %x, sc是 %x\n",i,sc);
if (i == sc){
puts("i == sc");
}else{
puts("i != sc");
}
putchar('\n');
printf("转成16进制后i是 %x, usc是 %x\n", i, usc);
if (i == usc){
puts("i == usc");
}else{
puts("i != usc"); 
}
return 0;
}

假如,我们有一个文件echo "this is my file" > myfile.txt

然后通过ghex编辑二进制文件,于是把this is my file的改成十六进制的表示, 使得十六进制文件包含0xff 0xff
0x74 0x68 0x69 0x73 0xff 0xff 0x73 0x20 0x6d 0x79 0x20 0x66 0x69 0x6c 0x65 0x0a

fgetc每次读一个unsigned char, 于是读的顺序为0x74 0x68 0x69 0x73 0xff 0xff 0x73 0x20 0x6d 0x79 0x20 0x66 0x69 0x6c 0x65 0x0a

用程序验证一下

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

int main(void){

  FILE *fp;

  int c;

  if ((fp = fopen("myfile.txt", "rb")) == NULL){

    perror("myfile.tx");

    return 0;

  }
  char buffer[33];

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

    //printf("%0x \t",c);
    //itoa(c, buffer, 16);
    sprintf(buffer, "%#x ", c);
    printf("%s", buffer);
  }
  printf("\n");

  fclose(fp);

  return 0;

}

从上面程序的运行结果来看,fgetc并没有把文件中的0xffff误当做EOF。如果fgetc每次读取的是一个int,也就是32个bit,那么它可能读到0xffff,有符号的int,就等于-1, 但是这种情况不会出现,因为fgetc每次读的是一个unsigned char。

类型转换

我们再来看看十进制整数-1,在不同存储空间和不同的类型下值分别是什么?

 

十进制 十六进制(int) 十六进制(char)
-1 ffff   ff

接着看char型从8位的值,补充为32位的值,在不同类型补充的值不同

十进制 十六进制char 由unsigned char补充为int的值 由signed char补充为int的值
-1 ff 00ff ffff

 于是根据上面的转型规则,一个陷阱出现了。

一个陷阱

单单从函数名上推测,很多人会认为fgetc返回一个是个8位的char类型的字符,所以程序上会写成

#include <stdio.h>

int main(void)
{

FILE *fp;

unsigned char c;


if ((fp = fopen("myfile.txt", "rb")) == NULL)
{

perror ("myfile.txt");

return 0;

}


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

putchar (c);

}


fclose(fp);

return 0;

}

 

文件结束时fgetc函数返回0xffff,但是被以上的程序变成了unsigned char了,于是根据类型把c补全到32位,变成了0x00ff。

由于0x00ff != 0xffff, 所以以上程序会出现一个死循环。

总结:

fgetc函数每次读一个8位的unsigned char,但是转型为32位的int返回,遇到文件结束返回0xffff,由于int的长度比char长,所以不可能读到负数。