补遗篇之文件I/O

文件I/O库是ANSI C标准的一部分,换句话,任何标准C语言开发环境中都可以用fopen/fread/fwrite/fclose接口访问文件。这里强调与文件I/O相关的几个易混淆的问题。

fopen/open的区别和联系

    unix/linux下文件IO有两套接口,即fopen/open系列,它们区别又联系:

    a. fopen是标准C库接口,所有C开发环境都要支持它;openposix标准接口,只有符合posix标准的操作系统(如linux)才支持。从可移植性考虑,一般文件操作应首选标准IO,即fopen

    b. fopen返回一个结构体指针(FILE *),open返回文件描述符fdint型数)。基于open实现fopen的过程可简单认为是:1)转换文件属性,open/fopen对文件属性各有定义,要进行映射转换;2)根据转换后的属性open文件,得到fd3)建立FILE结构体,并把fd/flag等属性赋给FILE中的成员变量,最后返回FILE *

    c. fread/fwrite内部有读写缓冲;read/write没有(但不一定直接读写磁盘,可能操作内核缓冲区)。频繁read/write意味着频繁进行系统调用(OS用户/内核态切换),效率很低。而fread/fwrite有内部缓存,可减少系统调用次数,效率提高很多(重要区别)。

    d. open不光用于操作通常意义的文件,linux系统下open是所有驱动的上层接口,如套接字、管道及其他linux硬件设备驱动等,这和fopen就没什么关系了。

扩展:从fd获取file path?

    某同事碰到一问题:集成某模块到系统框架,模块可获得上层open文件传下的fd,但原模块自身对外API只接收file path参数,从fd逆向得到file path?这不可能。file path参数无非是为提供给内部的fopen得到FILE *,于是增加以FILE *为参数的API,外部用fdopenfd)从fd得到FILE*指针,传递给新API,模块内部基于FILE *fread/fwrite处理流程不变。

fread/fwrite2/3参数

    fread/fwrite原型:size_t fread/fwrite( void *buffer, size_t size, size_t cnt, FILE *fp );  它的读写单位是数据记录,而不是byte,size代表所操作的记录大小,cnt为试图读写的记录数,返回值代表成功读写的记录数(不是字节数)。那么读写结构体T(buf, sizeof(T),1,stream),还是(buf, 1,sizeof(T), stream)?很多人每次都为此纠结,有的干脆不区分,只要size*cnt值固定,随便组合。但这种鸵鸟做法不是总能蒙混过关:文件大小4000bytesfread(buf,4096,1,fp)读取失败返回null,而fread(buf,1,4096,fp)会把4000 bytes数据读到buf并返回4000。类似在文件结尾fwrite,不同size/count组合,结果也可能不同。此外一般要求sizecount平衡且符合数据逻辑分块,如结构体数组struct XX y[10]写到文件最好fwrite(y,sizeof(XX),10,fp),不要fwrite(y,sizeof(XX)*10,1,fp);fwrite(y,1,sizeof(XX)*10,fp);

fread/fwrite返回值。

    fread/fwrite失败时返回null,有时还会返回与预设count不等的值。不能默认操作成功或仅检查是否为零,要检查返回值,根据所有可能的返回值设计后续流程,继续操作剩余buf——这里经常有bug

    a. fread/fwrite指定count2,当读写位置距文件末尾只有一条记录,返回1

    b.由于内部缓存有上限,fread/fwrite单次读写大小受限,如超过内部buf,只会按最大内部buf size读写。

fseek操作后的恢复

    fseek操作改变文件读写位置,但有时这种操作时暂时的,需要恢复原位使后续文件操作正确进行。例如有人常这样封装函数获取文件大小:

    int get_filesize(FILE *fp)

    {

        fseek(fp, 0, SEEK_END);

        f_len = ftell(fp);

        return f_len;

    }

    这里忘记fseek改变了文件读写位置,这个get_filesize函数会影响后续fread/fwrite,应用ftell记录原读写位置,返回前fseek还原。

遗漏fclose

    通过fopen打开文件并执行完IO操作后,如果忘记fclose会怎样?

    linuxfopen内部通过open得到一个fd,如果应用忘记fclose,该fd会被一直占用直到程序退出,称为描述符泄漏,类似内存泄漏。如果进程拥有的描述符资源全部泄漏,后面其他的open操作就会因得不到描述符而失败。

    另外C标准IO库有内部读写缓冲,fwrite可能把数据写到内部缓冲,fclose会在关闭文件前flush内部缓冲区,因此忘记fclose,就不能确保fwrite内容出现在文件中。

fopen的r/rb模式

    谈r/rb区别之前,另一对概念要澄清:文本文件与二进制文件。所有文件都使用了某种编码方式,其中:

    文本文件是为保存和传递人们定义和能理解的各种符号信息(各种语言、数字等),一般用定长字符编码(1 byte ascii2 bytes unicode),如"01000000_01000001",按ascii解码,对应字符'A''B',相应写字板/纪事本等文本工具只需简单查表译码即可解析出对应符号。但文本文件里每个含义至少要一个字符表达,空间利用率不高。这里C标准IO文本文件一般特指ascii文本文件,不再重复。

    除文本外的其他文件中,多少bits代表一个逻辑含义由特定文件格式定义,如BMP:前2bytes标识bmp格式,后面8bytes记录文件长度,再4bytes记录文件头长度……。使用类似自定义变长编码方式的文件统称二进制文件,它充分利用每bit,空间利用率高。但由于编码方式各异,不可能有word那样的通用解析工具,所以bmp要用photoshop编辑,而mp4文件photoshop却打不开,只能用暴风影音等软件播放。(记事本打开二进制文件得到乱码,看不到二进制内容,因为它用默认ascii码解析特定二进制格式,当然混乱。)

    一般把fopenr/rb标志称为以文本/二进制方式打开文件,这与文本/二进制文件是两个概念但我们要求以r方式打开文本文件,rb方式打开二进制文件。这是因为rb方式读时原样读出文件內容;写时把buf內容原样写到文件。而r则要针对一些控制符,如回车(CR)/换行(LF)/结束符EOF等做特殊处理。这些控制字符在文本文件和二进制文件中意义完全不同。因而C程序员混淆rrb方式时最常犯一些错误,如:

    a.r方式打开并读写二进制文件使文件读写因CR/LF符转换导致错误;

    b.判断文本及二进制文件结尾时,EOFfeof的混用

   关于这点可参考转载博文

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值