那些年,坑死自己的事之fread/fwrite

今天继续看牛人做过的东西,这个小程序并不大,加上相当多的注释行,才5000多行。这个小程序是在linux下实现的,之前自己也一直用vi来看并加以更加详细的注释,但是效率实在太低。于是将其转移到windows下决定改造到VS2012下运行。

这是一段纯C的代码,新建的工程是C++的,而代码中使用了强制类型转换将一个结构体类型转换成了另一个结构体。于是编译的时候报错不能通过。最后,我新建了空工程,将其以已存在的文件的形式导入,解决了这个问题。修改了一些问题之后,终于不报错,可以运行了。可是真正悲催的事情开始发生了。

首先,运行之后报错,确定了是文件读写的错误之后,给原来的代码中打开文件的地方加上异常处理。可是还是错误。单步进去发现

while(fread(&record,sizeof(RECORD_TYPE),1,fp_data)==1)

怎么都不能进入循环,fread的返回值永远都是0。因为sizeof(RECORD_TYPE)的值是128,于是把上面的代码改成了

while(fread(&record,1,128,fp_data)==128)

再来单步一看,好奇怪,这次返回的值变成了33。百思不得其解。之后我把这句代码拿出来,不放在while循环中,如下:

fseek(fp_data,0L,SEEK_END);//偏移都文件尾部
pos=ftell(fp_data);//读取尾部所在位置
fseek(fp_data,0L,SEEK_SET);//偏移到文件头部
err = fread(&recode,1,128,fp_data);//读取一条记录
pos = ftell(fp_data);//读取位置

之后再调试,发现前一个ftell得到的值是81792,而后一个ftell的返回值却是4096。更让人费解了。文件并没有到达文件末尾,文件足够大,才读取了4096/81792,可是,明明只读取了128位字节,128*8应该是1024才对,怎么第2个ftell返回了4096?这种情况下,有些慌了。虽然看了MSDN上的关于fread放回值得说明,可是却没有去实践。最后度娘告诉我这个网址http://www.360doc.com/content/11/0128/16/2150347_89591799.shtml

/*
文本方式读取二进制数据, 可能在文件结束之前将某段数据判定为文件末尾EOF, 所以结束读取( 举个例子, 比如遇到 0x00 0x00 0xff 0xff, 则文本方式方式的文件流, 认为已经到文件末尾, 不能读取)
 */

瞬间明白了错误的原因。对上一段代码修改一下再试:

fseek(fp_data,0L,SEEK_END);//偏移都文件尾部
pos=ftell(fp_data);//读取尾部所在位置
fseek(fp_data,0L,SEEK_SET);//偏移到文件头部
err = fread(&recode,1,128,fp_data);//读取一条记录
pos = ftell(fp_data);//读取位置
err = feof(fp_data);//是否到达文件尾,非0为经过了文件尾,0为否;feof具体用法见msdn

得到了feof返回值16,非0,表达经过了文件尾。因而断定fread读取数据的时候遇到了误以为的EOF标志。修正方法为,将fopen中的mode参数改成了‘rb’,即由文本方式读取改为二进制流的方式读取。如下:

//if (err=(fopen_s(&fp_data,"data","r")) != 0)
//    printf("open file data failed\n"); //打开数据文件

if (err=(fopen_s(&fp_data,"data","rb")) != 0)
    printf("open file data failed\n"); //打开数据文件

这个问题总算解决了。满以为就此解决了问题。偏偏陷入了令人更加头疼的境地。

再次点击运行,又是运行时错误。跟进去发现了出现了一个树的指针为空,却赋值了。再看明明调用了给这个节点申请了空间啊!难道是malloc失败?立马给malloc的地方加上判断。遗憾的是,依然一点进展都没有。再看作者的写代码的思路,给这个节点申请空间之前,先判断a的值是不是等于b,如果不等于,则打印一条消息新建树节点失败,但不终止程序,当然也不申请空间。噢,原来这样,那看看什么时候会出现这种情况。找到这段代码的上面一段代码,密密麻麻的一片,几个if-else写了一两百行。不过还是可以大致明白的。

头脑一震,发现问题了,作者代码大致如下:

if (a < b)
{
    //.......一大段代码,好几十行
}
else
{
    if (a != b)
    {
        print("");
        return FALSE;
    }
    //给节点申请空间
    //......一大段代码
}

所以我满心欢喜的认为即使作者这样的牛人也会犯迷糊,觉得理应将此处的a != b改成 a >= b。不管它能不能改,先改了再说。可是改了之后,运行一下,这次还是报错了,不过呢,不是这个地方了。单步调试,这个a的值怎么打大得太奇怪了吧。先看代码:

//此处a,b,c,d等都不是程序中的原样,只是为了说明而做了简化
    found=FALSE;
    if (a < b){
        i = 0;
        while (!found && i<a){
            if (c >=d[i]){//其中b就是数组d的大小
                i++;
            } else {
                found = TRUE;
            }
        }
        //其他代码
    }

再一看b的值是29,可是a的值却是5029。不发生越界才奇怪了。从此处也就明白了上一段代码中为何只判断a < b 及 a != b 而不处理 a > b 了,因为一旦 a > b 就出错了。

回头一想怎么会这样呢?

到底是哪儿错了???

想到了一点,会不会还是读取文件的时候的错误?会不会是因为以文本文件流方式读取了二进制文件,影响了a的取值?果断找到所有用到fopen的地方,把mode参数加上一个 ‘b‘ 。

再次点击运行,果然,问题就这么解决了。

总结

这么一场闹剧总算可以收场了,都是因为fread这个函数惹得祸。

/*
文本方式不能完全读取, 而二进制方式能的原因-
文本方式读取文件, 最主要的用处是一次读取一整句( 以换行符‘\n‘, 即二进制的换行标志"\r\n"结束 ), 方便用于特殊用处ReadString、fscanf(...,"%s",...)之类, 每次读取的内容长度是不定的; 而二进制读取方式Read、fread等, 都是读取固定长度
所以文本方式读取对EOF的判定, 是一个文件尾结束标志, 如果是文本文件, 则这个文件尾肯定不会出现在文件内容中( 因为是不可打印字符构成的结束标志, 人可读的文本文件不会包括它 ), 这样以结束标志为文件尾则是可以的; 二进制文件内容可以是任意字节, 如果把它当文本文件来读, 以文件尾为结束, 当然可能出现把文件内容判定为文件尾的情况;
二进制读取方式由于每次读取固定字节, 所以只需要用总文件长度( 这个数值是系统管理的数值, 不是计算得出来的 )减去每次读取的长度( 或根据Seek的位置计算长度 ), 就可以知道是否到文件尾, 不需要定义结束标志; 所以用二进制方式打开任何文件都是合理的
*/

至此也明白了作者是通过读取到的数据量是否等于fread 中count参数要求的数据量来结束循环而不是通过feof来判断。

最后,一定要明白,使用fread/fwrite的时候千万记得以二进制形式读取。

时间: 2024-11-10 01:31:27

那些年,坑死自己的事之fread/fwrite的相关文章

onInterceptTouchEvent onTouchEvent 的坑 坑死了

简单来说,不想研究这个,坑死了. onInterceptTouchEvent 是从父级向子级传递. onTouchEvent 是从子级向父级传递. 它们两个相互不影响,onInterceptTouchEvent 先触发,onTouchEvent 后触发. onInterceptTouchEvent 是ViewGroup的事件. onTouchEvent 是View 的事件,如果一个View 不能再包含子View ,则它没有 onInterceptTouchEvent 事件. 事件触发顺序为 Mo

被坑死了的中文乱码

最近在调试一个接口.然后,就被中文乱码给坑了. 事情的经过是这样的,领导让我跟其他服务器的接口进行对接,简单点就是我这边暴露一个http的请求地址给对方,然后对方发请求到我这边.然后,对方请求时,使用的是GBK编码的GET请求,然后,中文到我这边就乱码了. 最初,我这边的tomcat使用的是UTF-8编码.然后,接到对方请求后,怎么转都转不过来中文.当初不想改tomcat的配置,我就将(GBK.UTF-8.GB2312.ios-8895-1)这四种编码来回转啊!怎么就转不过来. 后来,没办法.那

一些Layout的坑。坑死我自己了

iOS这个东西,初学感觉,还好还好,然后一年之后再来修复一下初学的时候的代码,我只是感觉头很晕- - 别扶我. AutoLayout的坑,明明以前都没有的!!!升了iOS10就突然发现了这个坑,其实也有可能是以前就有,只是没踩到... 正点来了 当以前的我使用StoryBoard制定一系列的约束的时候,感觉屏幕适配都不是问题了! 然后以前的我突发奇想,不行,我要加一个代码控件,但是以前的我哪知道AutoLayout这个东西啊. 然后,就手写了个TextView,然后frame:CGRectMak

JQuery中动态生成元素的绑定事件(坑死宝宝了)

今天在做项目的时候,遇到了一个前端的问题,坑了我好长时间没有解决,今天就记录于此,也分享给大家. 问题是这样的,首先看看我的界面,有一个初始印象: 下面是操作列所对应的JS代码: { "data": function (datas) { return "<a data-url='/Device/Edit?id=" + datas.Id + "' data-toggle='modal' class='btn btn-sm btn-default btn

ZOJ问题(坑死了)

ZOJ问题 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 3221    Accepted Submission(s): 963 Problem Description 对给定的字符串(只包含'z','o','j'三种字符),判断他是否能AC. 是否AC的规则如下:1. zoj能AC:2. 若字符串形式为xzojx,则也能AC,其中x可

坑死水手(me)的错误updating

1.读懂题意,题意一定要走心读,不然不是坑队友,就是被队友坑 2.敲代码之前一定要先想好自己的思路,必须清晰明了,不然只能浪费时间 3.i++和j++任何时候都不要搞混了,我已经吃过好多次这个亏了,屡教不改T_T 4.考虑好数组的下标问题,关于数值大小以至于数据类型是int还是longlong,RE 5.函数名的各种手误写错,以及所在的头文件是什么,以免临比赛手忙脚乱 6.字符串结束标志是“\0”,不是“/0”........ 7.debug的时候要相信编译器可能会出问题,但是最好还是首先无限制

hdu 2822 ~!!!!!!坑死我

首先 在此哀悼...  为我逝去的时间哀悼...  每一步都确定再去写下一步吧...日狗 不过还是有点收获的..  对优先队列的使用 有了进一步的理解 先上代码 #include<iostream>#include<cstdio>#include<queue>#include<string.h>using namespace std;int dir[4][2]={{0,1},{0,-1},{1,0},{-1,0}};char mapp[1001][1001]

被Oracle坑死了

折腾了好久,终于好了,记录一下 win7x64:vs2013: Oracle client:64-bit ODAC 12c Release 4 (12.1.0.2.4) Xcopy for Windows x64 ODTwithODAC121024.zip   http://www.oracle.com/technetwork/database/windows/downloads/index-090165.html sqldeveloper: sqldeveloper-4.1.3.20.78-n

金山助手流氓软件-被进程sjk_daemon.exe坑死

修改完Android工程代码,进入调试阶段时DDMS中报错:The connection to adb is down, and a severe error has occured. 由于之前也碰到过这个问题,解决方法在DOS命令下进入ADT的工具目录,执行指令. D:\Tools\adt-bundle-windows-x86\sdk\platform-tools>adb kill-server D:\Tools\adt-bundle-windows-x86\sdk\platform-tool