程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 那些年,坑死自己的事之fread/fwrite,坑死freadfwrite

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

編輯:C++入門知識

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


今天繼續看牛人做過的東西,這個小程序並不大,加上相當多的注釋行,才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的時候千萬記得以二進制形式讀取。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved