c语言标准io中可读可写,C语言标准IO: [先读再feof] VS [先feof再读]
剛學習C語言讀取文件的時候,可能都遇到過這個“bug”,讀到末尾時數據有重復。
解決方案也是五花八門,甚至有人把數據先緩存了,再忽略掉最后一組....
不妨看一段代碼,兩種解決方案,猜猜看,究竟哪一個方案是正確的。
/*
*方案一:先判斷,后讀取
*/
while(!feof(fp))?{
fread(buf,?1,?100,?fp);
//do_some_thing
}
/*
*方案二:先讀取,后判斷
*/
while(1)?{
fread(buf,?1,?100,?fp);
if(feof(fp))
break;
//do_some_thing
}
/*
*方案一:先判斷,后讀取
*/
while (!feof(fp)) {
fread(buf, 1, 100, fp);
//do_some_thing
}
/*
*方案二:先讀取,后判斷
*/
while (1) {
fread(buf, 1, 100, fp);
if (feof(fp))
break;
//do_some_thing
}
曾經,我毫不猶豫的選擇一,過了不久又毫不猶豫的選擇二。然而,非常遺憾,沒有
一個是正確的。暫不解釋why,來一段MSDN上的代碼,關于feof的一個example:
#include?
#include?
intmain(void)
{
intcount,?total?=?0;
charbuffer[100];
FILE*stream;
fopen_s(?&stream,"crt_feof.txt","r");
if(?stream?==?NULL?)
exit(?1?);
//?Cycle?until?end?of?file?reached:
while(?!feof(?stream?)?)
{
//?Attempt?to?read?in?100?bytes:
count?=?fread(?buffer,sizeof(char),?100,?stream?);
if(?ferror(?stream?)?)??????{
perror("Read?error");
break;
}
//?Total?up?actual?bytes?read
total?+=?count;
}
printf("Number?of?bytes?read?=?%d/n",?total?);
fclose(?stream?);
}
#include
#include
int main( void )
{
int count, total = 0;
char buffer[100];
FILE *stream;
fopen_s( &stream, "crt_feof.txt", "r" );
if( stream == NULL )
exit( 1 );
// Cycle until end of file reached:
while( !feof( stream ) )
{
// Attempt to read in 100 bytes:
count = fread( buffer, sizeof( char ), 100, stream );
if( ferror( stream ) ) {
perror( "Read error" );
break;
}
// Total up actual bytes read
total += count;
}
printf( "Number of bytes read = %d/n", total );
fclose( stream );
}
不要驚訝,你沒看錯,MSDN是先使用feof,然后再讀取,跟網上的看法有些出入。
到底是為什么呢?我覺得,還是從feof和fread的源碼開始分析比較好,實現可能不同,用法應該是一致的。以GLIBC 2.10的源碼為例。不管是 unlocked 還是 非unlocked,feof最終還是調
用了unlocked的那個_IO_feof_unlocked“函數”,所以分析該“函數”即可。
#define?_IO_feof_unlocked(__fp)?(((__fp)->_flags?&?_IO_EOF_SEEN)?!=?0)
#define _IO_feof_unlocked(__fp) (((__fp)->_flags & _IO_EOF_SEEN) != 0)
現在可以得到一個結論:
feof的返回值,僅僅取決于文件結構是否被打上 _IO_EOF_SEEN 標志。
feof本身并不影響這個標志,因此可以斷定,標志是在讀取的時候被修改的,于是
去追尋fread的源碼。經歷了宏的泥淖,欣賞了純C填虛函數表模擬C++多態的壯
烈,終于找到了fread的背后黑手。fread實際上可能調用不同的函數,mmap文件
和普通文件的處理,是不一樣的,不過知道其中一個便可,因為底層的差異,對程
序員是透明的,只要展示給程序員看的一面是一致的就OK。且看關鍵部分代碼:
count?=?_IO_SYSREAD?(fp,?s,?count);
if(count?<=?0)
{
if(count?==?0)
fp->_flags?|=?_IO_EOF_SEEN;
else
fp->_flags?|=?_IO_ERR_SEEN;
break;
}
count = _IO_SYSREAD (fp, s, count);
if (count <= 0)
{
if (count == 0)
fp->_flags |= _IO_EOF_SEEN;
else
fp->_flags |= _IO_ERR_SEEN;
break;
}
看到_IO_SYSREAD有什么感覺呢? 哦,你可以把它當成表現跟read系統調用一樣
的東西,返回值大于0,就是實際讀取字節數;等于0,就是文件之前已被讀完(或者
是文件根本就是空的),本次沒讀到數據;小于0就是失敗,有錯誤發生。
總結代碼中的流程和細節,整理出來就是:
標準IO是帶有緩存的,每一次請求,未必對應一次系統調用
緩存剩余字節數大于等于請求的,直接使用緩存,不產生系統調用
fread當且僅當,系統調用讀取到的字節數為0時,才會打上結束標志
feof只檢查_IO_EOF_SEEN標志位,不做其它影響返回值的判斷
根據這些,不難得出以下結論:
文件全部讀完,即使緩存用盡且SYS_READ讀盡,feof未必返回真值
SYS_READ實際讀取到0字節的事情發生后,feof一定返回真值
fread實際讀取的字節數少于預期時,feof一定返回真值
feof可以只檢查標志就做出判斷,而fread可能多一次系統調用才知道結束
feof返回0的時候,上一次讀取的數據,一定是有效數據
至此,已經不難理解,為什么最上面貼的兩種方案都是錯的。因為它們都沒有對
fread的返回值做出反應,如果對這個返回值加以處理,無論是先讀后判斷還是先
判斷后讀取,都是沒有問題的,都能得到正確的結果。當然,這得有個前提,就是
除了feof外,有別的方法判斷是否結束,倘若如fgetc那般,讀取的字符作為函數返
回值,讀二進制文件時,就無法判斷了,因為二進制文件本身也可能含有EOF字符。
這種情況,只有一種方案,就是先讀取,再用feof判斷是否結束。
做了那么長時間的鋪墊,現在回到本文的核心。是先讀取還是先判斷?我認為:
在決定用哪一種方案前,首先要考慮用于讀取的那個函數的返回值的意義
如果你不想了解細節,也不屑于些許性能,一律先讀取后判斷,肯定不會有錯
如果讀取函數的返回值蘊含是否讀取成功,那么先判斷后讀取,可能更加高效
但是有一點,不管是哪種方式,如果用于讀取的函數,返回值包含讀取是否成功或者實際 讀取字節數等信息,這個信息是一定要考慮的。同樣,寫入操作也好,其它操作也好,只 要函數帶有返回值,且未注明這個返回值沒有意義,都應該認真推敲下這個返回值的意義, 然后決定是否需要處理這個返回值。
總結
以上是生活随笔為你收集整理的c语言标准io中可读可写,C语言标准IO: [先读再feof] VS [先feof再读]的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小学 n 阶乘的后面包含多少零c语言,C
- 下一篇: c语言 系统命令,c语言如何调用系统命令