c语言标准io中可读可写,C语言标准IO: [先读再feof] VS [先feof再读]
剛學(xué)習(xí)C語(yǔ)言讀取文件的時(shí)候,可能都遇到過(guò)這個(gè)“bug”,讀到末尾時(shí)數(shù)據(jù)有重復(fù)。
解決方案也是五花八門(mén),甚至有人把數(shù)據(jù)先緩存了,再忽略掉最后一組....
不妨看一段代碼,兩種解決方案,猜猜看,究竟哪一個(gè)方案是正確的。
/*
*方案一:先判斷,后讀取
*/
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
}
曾經(jīng),我毫不猶豫的選擇一,過(guò)了不久又毫不猶豫的選擇二。然而,非常遺憾,沒(méi)有
一個(gè)是正確的。暫不解釋why,來(lái)一段MSDN上的代碼,關(guān)于feof的一個(gè)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 );
}
不要驚訝,你沒(méi)看錯(cuò),MSDN是先使用feof,然后再讀取,跟網(wǎng)上的看法有些出入。
到底是為什么呢?我覺(jué)得,還是從feof和fread的源碼開(kāi)始分析比較好,實(shí)現(xiàn)可能不同,用法應(yīng)該是一致的。以GLIBC 2.10的源碼為例。不管是 unlocked 還是 非unlocked,feof最終還是調(diào)
用了unlocked的那個(gè)_IO_feof_unlocked“函數(shù)”,所以分析該“函數(shù)”即可。
#define?_IO_feof_unlocked(__fp)?(((__fp)->_flags?&?_IO_EOF_SEEN)?!=?0)
#define _IO_feof_unlocked(__fp) (((__fp)->_flags & _IO_EOF_SEEN) != 0)
現(xiàn)在可以得到一個(gè)結(jié)論:
feof的返回值,僅僅取決于文件結(jié)構(gòu)是否被打上 _IO_EOF_SEEN 標(biāo)志。
feof本身并不影響這個(gè)標(biāo)志,因此可以斷定,標(biāo)志是在讀取的時(shí)候被修改的,于是
去追尋fread的源碼。經(jīng)歷了宏的泥淖,欣賞了純C填虛函數(shù)表模擬C++多態(tài)的壯
烈,終于找到了fread的背后黑手。fread實(shí)際上可能調(diào)用不同的函數(shù),mmap文件
和普通文件的處理,是不一樣的,不過(guò)知道其中一個(gè)便可,因?yàn)榈讓拥牟町?#xff0c;對(duì)程
序員是透明的,只要展示給程序員看的一面是一致的就OK。且看關(guān)鍵部分代碼:
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有什么感覺(jué)呢? 哦,你可以把它當(dāng)成表現(xiàn)跟read系統(tǒng)調(diào)用一樣
的東西,返回值大于0,就是實(shí)際讀取字節(jié)數(shù);等于0,就是文件之前已被讀完(或者
是文件根本就是空的),本次沒(méi)讀到數(shù)據(jù);小于0就是失敗,有錯(cuò)誤發(fā)生。
總結(jié)代碼中的流程和細(xì)節(jié),整理出來(lái)就是:
標(biāo)準(zhǔn)IO是帶有緩存的,每一次請(qǐng)求,未必對(duì)應(yīng)一次系統(tǒng)調(diào)用
緩存剩余字節(jié)數(shù)大于等于請(qǐng)求的,直接使用緩存,不產(chǎn)生系統(tǒng)調(diào)用
fread當(dāng)且僅當(dāng),系統(tǒng)調(diào)用讀取到的字節(jié)數(shù)為0時(shí),才會(huì)打上結(jié)束標(biāo)志
feof只檢查_(kāi)IO_EOF_SEEN標(biāo)志位,不做其它影響返回值的判斷
根據(jù)這些,不難得出以下結(jié)論:
文件全部讀完,即使緩存用盡且SYS_READ讀盡,feof未必返回真值
SYS_READ實(shí)際讀取到0字節(jié)的事情發(fā)生后,feof一定返回真值
fread實(shí)際讀取的字節(jié)數(shù)少于預(yù)期時(shí),feof一定返回真值
feof可以只檢查標(biāo)志就做出判斷,而fread可能多一次系統(tǒng)調(diào)用才知道結(jié)束
feof返回0的時(shí)候,上一次讀取的數(shù)據(jù),一定是有效數(shù)據(jù)
至此,已經(jīng)不難理解,為什么最上面貼的兩種方案都是錯(cuò)的。因?yàn)樗鼈兌紱](méi)有對(duì)
fread的返回值做出反應(yīng),如果對(duì)這個(gè)返回值加以處理,無(wú)論是先讀后判斷還是先
判斷后讀取,都是沒(méi)有問(wèn)題的,都能得到正確的結(jié)果。當(dāng)然,這得有個(gè)前提,就是
除了feof外,有別的方法判斷是否結(jié)束,倘若如fgetc那般,讀取的字符作為函數(shù)返
回值,讀二進(jìn)制文件時(shí),就無(wú)法判斷了,因?yàn)槎M(jìn)制文件本身也可能含有EOF字符。
這種情況,只有一種方案,就是先讀取,再用feof判斷是否結(jié)束。
做了那么長(zhǎng)時(shí)間的鋪墊,現(xiàn)在回到本文的核心。是先讀取還是先判斷?我認(rèn)為:
在決定用哪一種方案前,首先要考慮用于讀取的那個(gè)函數(shù)的返回值的意義
如果你不想了解細(xì)節(jié),也不屑于些許性能,一律先讀取后判斷,肯定不會(huì)有錯(cuò)
如果讀取函數(shù)的返回值蘊(yùn)含是否讀取成功,那么先判斷后讀取,可能更加高效
但是有一點(diǎn),不管是哪種方式,如果用于讀取的函數(shù),返回值包含讀取是否成功或者實(shí)際 讀取字節(jié)數(shù)等信息,這個(gè)信息是一定要考慮的。同樣,寫(xiě)入操作也好,其它操作也好,只 要函數(shù)帶有返回值,且未注明這個(gè)返回值沒(méi)有意義,都應(yīng)該認(rèn)真推敲下這個(gè)返回值的意義, 然后決定是否需要處理這個(gè)返回值。
總結(jié)
以上是生活随笔為你收集整理的c语言标准io中可读可写,C语言标准IO: [先读再feof] VS [先feof再读]的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 小学 n 阶乘的后面包含多少零c语言,C
- 下一篇: c语言 误差小于10 -6,上海理工大学