在阮一峰的網絡日志里,阮先生寫了一篇關於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長,所以不可能讀到負數。
