刚学习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再读]相关推荐

  1. 计算机英语冯敏课后题答案,(中学篇)2020年第10期:例谈基于协同效应的读后续写教学(浙江:冯敏)一文涉及的读后续写试题...

    2020年1月浙江高考读后续写试题 阅读下面短文,根据所给情节进行续写,使之构成一个完整的故事. "I'm going to miss you so much, Poppy," s ...

  2. linux内存映射边读边写,内存映射IO空间的读写函数writeb(), writew(), writel()

    132 } IO_CONCAT定义在include/asm-$(arch)目录下的IO.H中 #define IO_CONCAT(a,b)_IO_CONCAT(a,b) #define _IO_CON ...

  3. 这是我读过写得最好的【秒杀系统架构】分析与实战!

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 来源:http://rrd.me/ewVWv 1 秒杀业务分析 2 ...

  4. java文件边读边写_[Java教程]node.js 利用流实现读写同步,边读边写

    [Java教程]node.js 利用流实现读写同步,边读边写 0 2017-09-10 13:00:14 //10个数 10个字节,每次读4b,写1blet fs=require("fs&q ...

  5. mac 修改文件权限为777可读可写可执行

    1,找到要修改的文件夹 2,sudo chmod -R 777 文件夹名. 3,输入管理员密码 chmod是更改文件的权限 chown是改改文件的属主与属组 chgrp只是更改文件的属组. 一.文件权 ...

  6. python读文件的三种方式_Python|读、写Excel文件(三种模块三种方式)

    python读写excel的方式有很多,不同的模块在读写的讲法上稍有区别: 用xlrd和xlwt进行excel读写: 用openpyxl进行excel读写: import xlrd from xlut ...

  7. 这是我读过写得最好的【秒杀系统架构】分析与实战!(转载)

    这是我读过写得最好的[秒杀系统架构]分析与实战!(转载) 1 秒杀业务分析 1. 正常电子商务流程 2. 秒杀业务的特性 2 秒杀技术挑战 1. 对现有网站业务造成冲击 2. 高并发下的应用.数据库负 ...

  8. 数据库优化专题---4、读多写少和读多写多

    数据库优化专题-1.表的主键用数字还是UUID 数据库优化专题-2.逻辑删除还是物理删除 数据库优化专题-3.千万记录如何快速分页 数据库优化专题-4.读多写少和读多写多 数据库优化专题-5.删改数据 ...

  9. NIO入门系列之第3章:从理论到实践:NIO 中的读和写

    3.1  概述 读和写是 I/O 的基本过程.从一个通道中读取很简单:只需创建一个缓冲区,然后让通道将数据读到这个缓冲区中.写入也相当简单:创建一个缓冲区,用数据填充它,然后让通道用这些数据来执行写入 ...

最新文章

  1. 【STM32】F1 系列驱动全彩显示屏
  2. EOS经济系统分析[转载]
  3. B站这套教程火了,火速搬运!限时删除~
  4. Web框架——Flask系列之Flask中的特殊变量和方法(十九)
  5. bootstrap 空行不显示横杠_bootstrap兼容问题
  6. 笨办法学 Python · 续 练习 42:SQL 删除
  7. 【原创】大端和小端字节序的细节
  8. 惠普服务器G8系列做raid,hp g8服务器设置raid5
  9. centos 添加windows字体库
  10. Trapcode Particular 5(合集·中英对照)
  11. Packet Tracer 思科模拟器入门教程
  12. 软件概要设计与详细设计
  13. 「最全」电子元器件图片、名称、符号图形对照(精编请收藏)
  14. 百度云上传本地图片到对象存储BOS——python版
  15. 【记录】mmsegmentation 训练自己的数据集
  16. ST官方的IIC实例解析(第一部分)
  17. 英雄联盟怎么解除小窗口_英雄联盟手游安妮怎么样 英雄联盟手游安妮技能介绍...
  18. usleep java_sleep()和usleep()的使用和区别
  19. html 图片自动滚动播放,javascript+html5实现仿flash滚动播放图片的方法
  20. 10-Stapler-可执行文件提权-内核提权-les.sh-linpeas.sh

热门文章

  1. mysql查询时有两条一模一样的结果应该只显示一条
  2. promise的应用和在VUE中使用axios发送AJAX请求服务器
  3. maven(二)pom文件详解
  4. 计算机网络【某个单位的网点由4个子网组成,结构如图所示,其中主机H1、H2、H3、H4的IP地址和子网掩码如表所示。 (1)请写出路由器R1到4个子网的路由表。(2)...】
  5. Redis-学习笔记01【Redis环境搭建】
  6. Android中CardView的简单使用
  7. 对称加密算法原理--OpenSSL演示、iOS代码运用及CCCrypt安全隐患
  8. JavaWeb学习之路——SpringBoot 中thymeleaf模板用法(三)
  9. 关于谷歌地图无法获取到WebGL上下文问题
  10. Executors.newFixedThreadPool和ArrayBlockingQueue一点使用心得