fwrite和fread的破皮探讨

好的,第一篇博文

灵感来源于《C缺陷与指针》里的一小段玩意,突然有兴趣就一直扣下去了

向下兼容也叫向后兼容(Backwards Compatibility)

百度后的大概意思就是:发行了一个程序的新版本后,即便用旧版本程序创建的文档/系统仍然能被新版本程序使用,就好像用Powerpoint1997做的ppt仍然能够被Powerpoint2003编辑一样。

在询问大佬后,得知在C语言中旧的C标准、C库中对于每个FILE对象不能同时进行读写操作,否则会引起文件的偏移量混乱。为了保持兼容旧标准,我们写的代码也有必要保证FILE对象不能同时进行读写操作。下面是书上的一段代码和解释(这两段代码的fseek使用似乎都有问题)

FILE *fp;
 2 struct record rec;
 3 ...
 4 while(fread((char *)&rec , sizeof(rec) , 1 , fp) == 1)
 5 {
 6     /*对rec执行某些操作*/
 7     if(/*rec必须被重新写入*/8         {
 9         fseek(fp , -(long)sizeof(rec) , 1);
10         fwrite( (char *)&rec , sizeof(rec) , 1 ,fp);
11      //fseek(fp , 0L .1);
12         }
13 }
书内解释:

这段代码看上去毫无问题:&rec在传入fread和fwrite函数时被小心翼翼地转换为字符指针类型,sizeof(long)被转换为长整型(fseek函数要求第二个参数是long类型,因为int类型的整数可能无法包含一个文件的大小;sizeof返回一个unsigned值,因此首先必须将其转换为有符号类型才有可能将其反号)。但这段代码仍然可能运行失败,而且出错的方式非常难于察觉。

问题出在:如果一个记录需要被重新写入文件,也就是说,fwrite函数得到执行,对这个文件执行的下一个操作将是循环开始的fread函数。因为在fwrite函数调用与fread函数调用之间缺少了一个fseek的函数调用,所以无法进行上述操作,解决方法是把注释中代码加上去。第二个fseek代码看上去什么也没做,但它改变了文件的状态。

以上是书内解释

那么问题来了,为什么就是不能同时R/W,交叉R/W就会引起文件偏移量混乱呢?

这涉及到fwrite和fread的内部运作原理,以及fseek的作用。

 

今天先探讨fwrite和fread的内部运作原理。下面是一个CSDN上的问题及回答,很详细。

因为他讲得很好我不认为我能讲得更好,我进行了部分的摘抄,如果有版权问题请通知我一下我马上处理,这是原址

#include <stdio.h>

int main()
{
    FILE *testFile = NULL;
    int ret = 0;
    char string[200] = {'\0'};

    testFile=fopen("test1.txt", "rb+"); //test1.txt的内容是abcdefghijklmnopqrstuvwxyz
    if(testFile == NULL)
    {
        return 0;
    }
    ret = fread(string, 1, 10, testFile);
    //ret = fseek(testFile, 0, SEEK_CUR);
    ret = fwrite("hello world", 1, 11, testFile);
    fclose(testFile); //test1.txt的内容还是abcdefghijklmnopqrstuvwxyz
    //如果fread和fwrite之间调用了fseek,文件内容变成abcdefghijhello worldvwxyz

    testFile=fopen("test2.txt", "rb+"); //test2.txt的内容也是abcdefghijklmnopqrstuvwxyz
    if(testFile == NULL)
    {
        return 0;
    }
    ret = fwrite("hello world", 1, 11, testFile);
    //ret = fseek(testFile, 0, SEEK_CUR);
    ret = fread(string, 1, 10, testFile);
    fclose(testFile); //test2.txt的内容变成hello worldヘヘヘヘヘヘヘヘヘヘvwxyz
    //如果fread和fwrite之间调用了fseek,文件内容变成hello worldlmnopqrstuvwxyz

    return 0;
}

回答大概如下:

程序和文件的交互都是通过缓存在完成的。fread和fwrite也是通过操作缓存(如果设置了缓存的话)来完成的

那fwrite、fread是如何实现这种交互的呢。这三者都是通过对FILE结构进行操作的,先看看FILE

struct _iobuf {
        char *_ptr;//文件缓存的当前位置
        int   _cnt;//缓存里可以读取的字节数
        char *_base;//文件缓存的起始位置
        int   _flag;
        int   _file;
        int   _charbuf;
        int   _bufsiz;//缓存大小
        char *_tmpfname;
        };
typedef struct _iobuf FILE;
  • FILE:这个结构其实包含的是文件信息(_flag,_file_,_tmpfname)和缓存信息(_ptr,_cnt,_base,_charbuf,_bufsiz)。

以此为基础,我们往下看

fread的简化过程如下,具体实现过程的代码在这里

当打开一个文件,并进行读取时,会先从磁盘读取一部分数据到缓存里,这时候_base指向缓存的起始地址,_ptr会根据读取的数量进行移动,_cnt则根据读取的数量进行减少。

fwrite的简化过程如下,具体实现过程的代码在这里

当打开一个文件,并进行写操作时,会先分配一块缓存,这时候_base指向缓存的起始地址,_ptr会根据写入缓存的数量进行移动。

有问题的代码中,取后半段代码即fwrite后fread为讨论对象

在调用一次fwrite和一次fread后,_ptr指针的位置距离_base为21(11+10)。那么,最后的close在flush的时候会把[_base,_ptr]之间的内容输入到文件中对应的位置。也就是说fwrite写入的hello world和fread调用导致_ptr移动的部分数据也被写入到文件了,这样就导致覆盖了文件里的部分数据。整个fwrite后fread的过程分为以下五步

  1. 打开文件
  2. 建立缓存,这个缓存里的数据是未初始化的4096字节,
  3. fwrite写入hello world在0-10字节里
  4. fread使得_ptr跳过11-20字节
  5. flush使得0-20字节写入文件,其中0-10是hello world,11-20未初始化

因为读写用的是同一块缓存,所以读之后再写要重置缓存,写之后再读也要重置缓存。否则两种操作会导致缓存里的数据错乱。

那么下一个问题,fseek是如何重置缓存的呢?

在《C与指针》P317页中提到fseek允许你从写入模式切换到读取模式,也就是

fseek(fp , 0L .1);

这一句并非没用的原因,他改变了文件的读写状态。具体为何他能够改变文件读写状态。上网翻了下

首先在CSDN论坛上也翻到了类似的问题,原址在这里

里面有回答是摘自MSDN98中fopen说明的,有这么一段

When the "r+", "w+", or "a+" access type is specified, both reading and writing are allowed (the file is said to be open for “update”). However, when you switch between reading and writing, there must be an intervening fflush, fsetpos, fseek, or rewind operation. The current position can be specified for the fsetpos or fseek operation, if desired.

翻译过来就是说:

当fopn以“r+”(读写方式打开,文件必须存在)、“w+”(打开可读/写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件)或“a+"(以附加方式打开可读/写的文件。若文件不存在,则会建立该文件,如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(原来的 EOF 符不保留))的方式执行的时候。读和写都是可以进行的(打开是为了”更新“的)。但是当在读和写之间转换的时候,这两者时间必须有个fflush、fsetpos、fseek或rewind操作。如果有必要的话,使用的位置(我猜应该是文件指针偏移量)可以用fsetpos和fseek指定

 

至于对源码的研究,目前的水平还看不懂,只能说记得用fseek记得转换文件读写状态避免文件指针出错

转载于:https://www.cnblogs.com/huiyuelin/p/7451151.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、效、安全和并发的编程语言,特别适用于构建性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、效、安全、并发的编程语言,特别适用于构建性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、效、安全和并发的编程语言,特别适用于构建性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、效、安全、并发的编程语言,特别适用于构建性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、效、安全和并发的编程语言,特别适用于构建性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、效、安全、并发的编程语言,特别适用于构建性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值