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判断是否结束。

做了那么长时间的铺垫,现在回到本文的核心。是先读取还是先判断?我认为:

在决定用哪一种方案前,首先要考虑用于读取的那个函数的返回值的意义

如果你不想了解细节,也不屑于些许性能,一律先读取后判断,肯定不会有错

如果读取函数的返回值蕴含是否读取成功,那么先判断后读取,可能更加高效

但是有一点,不管是哪种方式,如果用于读取的函数,返回值包含读取是否成功或者实际 读取字节数等信息,这个信息是一定要考虑的。同样,写入操作也好,其它操作也好,只 要函数带有返回值,且未注明这个返回值没有意义,都应该认真推敲下这个返回值的意义, 然后决定是否需要处理这个返回值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值