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再读]相关推荐
- 计算机英语冯敏课后题答案,(中学篇)2020年第10期:例谈基于协同效应的读后续写教学(浙江:冯敏)一文涉及的读后续写试题...
2020年1月浙江高考读后续写试题 阅读下面短文,根据所给情节进行续写,使之构成一个完整的故事. "I'm going to miss you so much, Poppy," s ...
- 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 ...
- 这是我读过写得最好的【秒杀系统架构】分析与实战!
点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 来源:http://rrd.me/ewVWv 1 秒杀业务分析 2 ...
- java文件边读边写_[Java教程]node.js 利用流实现读写同步,边读边写
[Java教程]node.js 利用流实现读写同步,边读边写 0 2017-09-10 13:00:14 //10个数 10个字节,每次读4b,写1blet fs=require("fs&q ...
- mac 修改文件权限为777可读可写可执行
1,找到要修改的文件夹 2,sudo chmod -R 777 文件夹名. 3,输入管理员密码 chmod是更改文件的权限 chown是改改文件的属主与属组 chgrp只是更改文件的属组. 一.文件权 ...
- python读文件的三种方式_Python|读、写Excel文件(三种模块三种方式)
python读写excel的方式有很多,不同的模块在读写的讲法上稍有区别: 用xlrd和xlwt进行excel读写: 用openpyxl进行excel读写: import xlrd from xlut ...
- 这是我读过写得最好的【秒杀系统架构】分析与实战!(转载)
这是我读过写得最好的[秒杀系统架构]分析与实战!(转载) 1 秒杀业务分析 1. 正常电子商务流程 2. 秒杀业务的特性 2 秒杀技术挑战 1. 对现有网站业务造成冲击 2. 高并发下的应用.数据库负 ...
- 数据库优化专题---4、读多写少和读多写多
数据库优化专题-1.表的主键用数字还是UUID 数据库优化专题-2.逻辑删除还是物理删除 数据库优化专题-3.千万记录如何快速分页 数据库优化专题-4.读多写少和读多写多 数据库优化专题-5.删改数据 ...
- NIO入门系列之第3章:从理论到实践:NIO 中的读和写
3.1 概述 读和写是 I/O 的基本过程.从一个通道中读取很简单:只需创建一个缓冲区,然后让通道将数据读到这个缓冲区中.写入也相当简单:创建一个缓冲区,用数据填充它,然后让通道用这些数据来执行写入 ...
最新文章
- 【STM32】F1 系列驱动全彩显示屏
- EOS经济系统分析[转载]
- B站这套教程火了,火速搬运!限时删除~
- Web框架——Flask系列之Flask中的特殊变量和方法(十九)
- bootstrap 空行不显示横杠_bootstrap兼容问题
- 笨办法学 Python · 续 练习 42:SQL 删除
- 【原创】大端和小端字节序的细节
- 惠普服务器G8系列做raid,hp g8服务器设置raid5
- centos 添加windows字体库
- Trapcode Particular 5(合集·中英对照)
- Packet Tracer 思科模拟器入门教程
- 软件概要设计与详细设计
- 「最全」电子元器件图片、名称、符号图形对照(精编请收藏)
- 百度云上传本地图片到对象存储BOS——python版
- 【记录】mmsegmentation 训练自己的数据集
- ST官方的IIC实例解析(第一部分)
- 英雄联盟怎么解除小窗口_英雄联盟手游安妮怎么样 英雄联盟手游安妮技能介绍...
- usleep java_sleep()和usleep()的使用和区别
- html 图片自动滚动播放,javascript+html5实现仿flash滚动播放图片的方法
- 10-Stapler-可执行文件提权-内核提权-les.sh-linpeas.sh
热门文章
- mysql查询时有两条一模一样的结果应该只显示一条
- promise的应用和在VUE中使用axios发送AJAX请求服务器
- maven(二)pom文件详解
- 计算机网络【某个单位的网点由4个子网组成,结构如图所示,其中主机H1、H2、H3、H4的IP地址和子网掩码如表所示。 (1)请写出路由器R1到4个子网的路由表。(2)...】
- Redis-学习笔记01【Redis环境搭建】
- Android中CardView的简单使用
- 对称加密算法原理--OpenSSL演示、iOS代码运用及CCCrypt安全隐患
- JavaWeb学习之路——SpringBoot 中thymeleaf模板用法(三)
- 关于谷歌地图无法获取到WebGL上下文问题
- Executors.newFixedThreadPool和ArrayBlockingQueue一点使用心得