1.c和java的老師都說讀文件讀到-1就意味著文件讀完,這是否意味著文件的內容不可能出現-1(因為一旦有了-1就意味著文件到此為止)?

2.如果文件內容里可以出現-1,那程序怎麼知道這個不是eof而是真正的文件值?

謝謝大家栽培


先來回答你的問題:

一切文件的結尾都是值為-1的位元組嗎?

Of course not!!! 一般的文件系統下文件的結尾沒有特殊位元組。碰到文件結尾返回EOF是一些函數的約定操作,不是因為它們真的在結尾讀到了-1的內容。

1.c和java的老師都說讀文件讀到-1就意味著文件讀完,這是否意味著文件的內容不可能出現-1(因為一旦有了-1就意味著文件到此為止)?

答:其實以EOF判斷讀到文件結尾是stream IO里類似fgetc()常做的事。我想你想說的應該是用的這樣的函數。實際上軟體中一般獲取文件內容用的是read, mmap等系列函數,至於判斷結尾更不是EOF的事兒了。所以即使是對於fgetc()這樣的函數,讀到-1就意味文件讀完也是錯誤的,或者說是片面的。文件是有可能讀出-1的,-1無非就是0xffffffff...,這樣的內容在文件里完全無可厚非。而且在中途出現錯誤時也可能返回-1。所以如果你的程序選擇碰到-1就返回是不好的寫法。

2.如果文件內容里可以出現-1,那程序怎麼知道這個不是eof而是真正的文件值?

答:類似feof()之類的函數可以幫助你判斷文件是不是真的到了結尾。其實-1和-1有時也是不一樣的,比如你用fgetc()得到的-1其實是0xff,而EOF一般是0xffffffff。當它們都存儲在int型變數里時,0xff和0xffffffff是不相等的,只有最後fgetc()發現錯誤或文件結尾時才返回0xffffffff。

下面具體看一下:

我們以test-eof.c為例:

#include &

int main(int argc, char *argv[])
{
FILE *file;
int n = 0;
int rc;
//char rc;

printf("EOF=%x
", EOF);
file = fopen("testfile", "r");

do {
rc = fgetc(file);
printf("0x%x ", rc);
if ((++n) % 16 == 0)
printf("
");
}while(rc != EOF);
printf("
");

return 0;
}

測試如下:

  • 編譯

# gcc -o test-eof test-eof.c -Wall

  • 準備一個叫testfile的文件,文件內容是開頭16個位元組是0x11, 然後緊接著16個位元組是0xff,最後再來16個0x22

xfs_io -t -f -c "pwrite -S 0x11 0 16" -c "pwrite -S 0xff 16 16" -c "pwrite -S 0x22 32 16" testfile
wrote 16/16 bytes at offset 0
16.000000 bytes, 1 ops; 0.0000 sec (578.704 KiB/sec and 37037.0370 ops/sec)
wrote 16/16 bytes at offset 16
16.000000 bytes, 1 ops; 0.0000 sec (2.543 MiB/sec and 166666.6667 ops/sec)
wrote 16/16 bytes at offset 32
16.000000 bytes, 1 ops; 0.0000 sec (8 MiB/sec and 500000.0000 ops/sec)

  • 檢查一下文件內容確實如我們所設計

# hexdump testfile
0000000 1111 1111 1111 1111 1111 1111 1111 1111
0000010 ffff ffff ffff ffff ffff ffff ffff ffff
0000020 2222 2222 2222 2222 2222 2222 2222 2222
0000030

  • 開始執行test-eof,上面的程序會輸出什麼,會在那堆0xfffff...處就結束嗎?

.....

先想一下

......

......

# ./test-eof
EOF=ffffffff
0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11
0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
0x22 0x22 0x22 0x22 0x22 0x22 0x22 0x22 0x22 0x22 0x22 0x22 0x22 0x22 0x22 0x22
0xffffffff

答案是不會。為什麼?就像我們上面說的,因為即使都是-1,八位有符號的-1和32位有符號-1是不一樣的。我們看一下fgetc的manual是怎麼說的:

# man fgetc
FGETC(3) Linux Programmers Manual FGETC(3)

NAME
fgetc, fgets, getc, getchar, ungetc - input of characters and strings

SYNOPSIS
#include &

int fgetc(FILE *stream);
...
...
DESCRIPTION
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.
...
...
RETURN VALUE
fgetc(), getc() and getchar() return the character read as an unsigned char cast to an int or EOF
on end of file or error.
...
...

文檔說的很清楚,fgetc()是讀一個unsigned char類型的數據,然後把這個unsigned char類型的數據返回並賦值給一個int類型的數據,或者在文件結尾或發生錯誤時返回EOF。也就是說fgetc()在正常讀取的時候是讀一個8位無符號整形的,然後把它存儲在一個int型的變數里返回。所以當它讀到一堆0xfffff....的時候它是一個0xff一個0xff的讀的,把一個無符號0xff轉存到int類型時還是0xff(相當於int型的255)。但是EOF是int型的-1,存儲格式是0xffffffff。0xff不等於0xffffffff。

但是你可能注意到我的那行注釋掉的rc定義了:

//char rc;

我們按照fgetc()的標準,對其返回值的存儲變數定義為int型。但是如果我已經們知道fgetc()不會獲取超過0xff的數,所以如果把它定義為char型呢?結果會怎麼樣呢?會在那堆0xff處返回嗎?

......

......

......

答案是肯定的:

# ./test-eof
EOF=ffffffff
0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11
0xffffffff

為什麼?因為char型的0xff就不是255了,就真的是-1了,即使擴展為int型也是0xffffffff,是和EOF相等的存在。所以讀到那堆0xff的時候就以為讀到了EOF,而停止了。

實際上將fgetc()的返回值定義為char是非常不合規矩的行為,也不建議這樣做。同時如果真的想判斷eof,可以藉助feof():

# man feof
FERROR(3) Linux Programmers Manual FERROR(3)

NAME
clearerr, feof, ferror, fileno - check and reset stream status

SYNOPSIS
#include &

void clearerr(FILE *stream);

int feof(FILE *stream);
....
....
The function feof() tests the end-of-file indicator for the stream pointed to by stream, return‐
ing nonzero if it is set. The end-of-file indicator can be cleared only by the function clear‐
err().
....

feof的作用就是判斷當前的stream是否到了EOF,如果到了就返回非零(即真)。

那麼用它做如下測試:

#include &

int main(int argc, char *argv[])
{
FILE *file;
int n = 0;
//int rc;
char rc;

printf("EOF=%x
", EOF);
file = fopen("testfile", "r");

do {
rc = fgetc(file);
printf("0x%x ", rc);
if ((++n) % 16 == 0)
printf("
");
if (rc == EOF) {
if (feof(file))
break;
}
}while(1);
printf("
");

return 0;
}

我們還是以錯誤的將fgetc()的返回值定義為char型來測試,看feof能不能幫助我們準確定位EOF?

......

......

......

# ./test-eof
EOF=ffffffff
0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11
0xffffffff 0xffffffff 0xffffffff 0xffffffff 0xffffffff 0xffffffff 0xffffffff 0xffffffff 0xffffffff 0xffffffff 0xffffffff 0xffffffff 0xffffffff 0xffffffff 0xffffffff 0xffffffff
0x22 0x22 0x22 0x22 0x22 0x22 0x22 0x22 0x22 0x22 0x22 0x22 0x22 0x22 0x22 0x22
0xffffffff

答案是它可以。我們可以看到中間那堆0xffffffff的輸出,如果不是feof()的幫忙,在第一個0xffffffff時已經返回了。

總結:

程序設計並像你們學習時老師講的那麼簡單,而I/O操作更是程序設計的重點難點。實際上的IO操作是很複雜的,更是多種多樣的。鑒於你可能是初學,說多了也沒用。建議你可以在學會C語言語法後嘗試學習類似《Unix環境高級編程》這樣的書稍微進階一下,也能加深互相理解,單純的C語言語法是沒有太大用處的。程序設計本身絕對不是語言語法的那點事兒,而是要對系統有比較深入的理解。


首先,文件的內容結尾是什麼是沒有限制的。常見的文件操作函數只是發現文件讀完了給你返回-1,也就是說,這個-1是這個函數自作主張給你的,用以提醒你文件讀完了。


不是

以fgetc為例,從文件中讀到的內容是一個位元組,值域0~255(1byte按8bit算),根本不包括-1,談何讀到呢

但fgetc是可以返回-1的,這時候表示文件結束,而非讀到某個位元組了


除非平台把位元組的位數定義到至少16位,並且至少和int的長度一樣,否則-1根本就不在合法字元的範圍里。(不過如果真的這樣,fgetc就沒法用了)


以 fread 為例

size_t fread( void *buffer, size_t size, size_t count,
FILE *stream )

這裡你是通過返回值來判斷是否讀到 EOF 的,而不是說 buffer 裡面有沒有讀到 -1。

EOF 只是標誌了狀態,跟文件內容無關,畢竟文件就是個二進位流。你老師所謂的讀到 -1 意思是應該是讀到返回值為 -1。

但是這裡返回 -1 還有可能是讀取的時候發生了其他錯誤,下一步應該是用 feof 和 ferror 去檢查究竟是遇到了 EOF 還是 Error。

最後,雖然通常情況下 EOF 的確是 -1,但是印象中標準中只規定了 EOF 為負即可,所以說 EOF 是 -1 -2 還是 -3 都是有可能的。


不知道是你沒仔細聽,還是老師講的不對。準確地說,當用getc、getchar這兩個函數讀文件時,如果返回-1,代表遇到了文件尾。請注意,getc的返回值類型是int(通常是四個位元組),不是unsigned char。正常返回的字元都是在unsigned char的範圍內的,是0x00000000到0x000000FF。而-1的十六進位表示是0xFFFFFFFF。所以正常返回的字元跟-1這個標誌不會衝突。當你判斷了返回值不是-1,也就是確實得到了一個字元,然後你可以把它保存在一個char或者unsigned char變數里。寫成代碼的話,就是

char c;
int r;
r=getchar();
if (r!=-1)
c=r;

這時候要當心了,c是個char,它是有符號整數,值域是-128~127。它的值有可能是-1,但是這個-1是文件里一個字元的真實值,不是EOF標誌。

如果讀文件用的不是getc,而是fread,那麼返回0才表示讀完了,文件里是否有-1字元,跟文件是否結束毫無關係。


你理解錯了,文件內容是在放在char *參數中的,而返回值-1是fread函數本身的返回值,和文件內容沒有任何關係,fread應該是首先獲取文件大小,然後使用文件指針一個個讀取放在char *參數中,如果讀到超過文件大小,就返回-1即eof。想一下你如果自己寫一個fread函數應該怎麼寫?或者簡單一些,寫一個strcpy函數,內部使用 來判斷字元串是否結束,但返回的並不是 而是字元串長度吧。


fget返回int,明白嗎?

遇到內容為11111111的位元組,返回值是:

00000000 00000000 00000000 11111111

(255)

遇到文件尾部的返回值是:

11111111 11111111 11111111 11111111

(-1)

-1不是一個位元組的-1,是四個位元組的。


值為-1的位元組即0xff,所以答案是NO。

文件的結尾都是值為-1的整數,或者說是int。要知道int一般是4位元組長,和0xff是不相等的。


推薦閱讀:
相关文章