上次說日本海嘯警報的時候,程式出錯。在解析**的時候,發現了mfc中的乙個bug。
一。問題的產生。
這個程式,用來處理日本各種天氣預報資料,包括災害的預報。如果**,颱風之類的自然災害到來,程式會把預報資料進行處理,生成相應的警報資訊,並在電視上面顯示滾動的字幕來提示。程式本身,是幾年前公司的其他人寫的。裡面有涉及到檔案讀寫的地方,有很多地方,用了mfc中自帶的檔案讀寫類cstdiofile。
cstdiofile這個檔案讀寫類,估計大家都不陌生。這個類的父類,是cfile類。cstdiofile類本身的功能也很簡單。cstdiofile類有乙個成員函式是readstring,函式的定義如下:
virtual lptstr readstring(__out_ecount_z(nmax) lptstr lpsz, __in uint nmax);
virtual bool readstring(cstring& rstring);
msdn定義如下http://msdn.microsoft.com/library/x5t0zfyf(vs.80).aspx:
bool readstring(cstring& rstring);
throw( cfileexception );
return value
a pointer to the buffer containing the text data. null if end-of-file was reached without reading any data; orif boolean, false if end-of-file was reached without reading any data.
readstring函式能直接讀取文字中的一行資料到cstring中,很方便。讀到檔案結尾,沒有讀出任何資料的時候,返回false。很簡單的函式,但恰恰是這個函式有bug。
程式在處理資料的時候,會生成一些臨時檔案,然後會讀取這些臨時檔案中的資料,讀取操作,正是用的cstdiofile的readstring函式。讀取流程很簡單:
while(dfile.readstring(str_temp))
當時的現象為,讀取到最後一行,總是直接返回false,怎麼也讀不出最後一行來。看了看檔案的最後一行,包含2176個字元的資料,沒有換行符。沒有任何異常啊。當時沒想到是mfc的bug,因為以前有這樣那樣的毛病,多數是預報資料本身有問題,所以這次也是先分析資料了。分析來分析去,沒發現這次的資料有什麼異常。後來發現如果最後一行的檔案不是2176個字元,就能正常讀出來。奇了怪了,2176也不是什麼特殊長度啊。實驗了幾次後,覺的是在不對勁。莫非是mfc的bug?
二。發現問題所在
決定看看mfc的**再說。做了個簡單的測試程式,跟到mfc**裡一看,果然是mfc的問題!測試**如下:
cstdiofile dfile;
dfile.open("text.txt",cfile::moderead);
cstring str;
while (dfile.readstring(str) != false )
dfile.close();
測試**很簡單,讀text.txt檔案中的每一行,然後列印出來。還是2176個字元就不行。確定了不是資料的問題,就是mfc**本身的bug。
mfc的readstring**如下:(中文是我加的注釋)
bool cstdiofile::readstring(cstring& rstring)
// if string is read completely or eof
if (lpszresult == null ||
(nlen = lstrlen(lpsz)) < nmaxsize ||
lpsz[nlen-1] == '/n')
break;
nlen = rstring.getlength();
lpsz = rstring.getbuffer(nmaxsize + nlen) + nlen; //位置後移
}
// remove '/n' from end of string if present
lpsz = rstring.getbuffer(0);
nlen = rstring.getlength();
if (nlen != 0 && lpsz[nlen-1] == '/n') // 最後結果中,去掉回車符
rstring.getbuffersetlength(nlen-1);
return lpszresult != null; // 這裡就是bug的關鍵。返回值不對!
}
可以看到,readstring的底層,是用fgets來讀取檔案的。在內部,每次讀取128個字元到cstring中,然後位置後移,反覆讀取128個字元,直到遇到回車符或者檔案結束。最後把回車符去掉,返回乙個cstring。其中,lpszresult也指向每次讀出的字串。
這裡就看出問題所在了,2176個字元,正好是128的17倍!也就是說,只要檔案最後一行是128倍數個字元,就一定會返回false。
為什麼會這樣呢,因為readstring在每次讀取128個字元的時候,用lpszresult指向讀取到的字串。如果讀滿了128個字元,就繼續讀,如果讀到的字元不夠128個,那麼就結束讀取。
當一行資料正好為128的倍數,又沒有回車符的時候,會發生什麼呢?比如最後一行資料是128個,那麼,讀一次128個字元,會繼續讀下一次,但是下一次的讀取,什麼也沒有讀到,lpszresult就指向null,最後的返回值,是return lpszresult != null; 所以返回false。
但之前讀到的128個字元,已經在cstring裡面了。
也就是說實際上讀取已經成功了,但還是返回了false。返回值不恰當!
bug的描述:當檔案的最後一行資料,正好是128的倍數個字元的時候,用
readstring讀取,一定會返回false。但實際上讀取是成功的,返回的cstring中的資料是正確的!(vc6.0中存在這個bug,vs2005中,沒有這個bug)
這個bug,只會影響到最後一行資料。因為如果有換行符的存在,lpszresult就不會為null。
三。解決方法
要解決這個問題,也簡單,修改一下判斷readstring成功與否的語句:
while (dfile.readstring(str) != false || str.getlength() != 0)
在返回false的情況下,cstring的長度不為0,就不算讀取失敗。或者這樣:
if(!dfile.readstring(str) && str.getlength() == 0)
在返回false並且cstring的長度為0,則算讀取失敗,否則就是讀取成功。
這個程式,是用vc6.0做的,我有看了看vc2005中的**,發現這個bug被修復了,**如下:
bool cstdiofile::readstring(cstring& rstring)
// if string is read completely or eof
if (lpszresult == null ||
(nlen = (int)lstrlen(lpsz)) < nmaxsize ||
lpsz[nlen-1] == '/n')
break;
nlen = rstring.getlength();
lpsz = rstring.getbuffer(nmaxsize + nlen) + nlen;
}
// remove '/n' from end of string if present
lpsz = rstring.getbuffer(0);
nlen = rstring.getlength();
if (nlen != 0 && lpsz[nlen-1] == '/n')
rstring.getbuffersetlength(nlen-1);
return nlen != 0; //返回值變了!
}
我們看到,vc2005中,讀取部分的**與vc6.0中的**完全一樣。不一樣的地方只是返回值的部分。vc2005的readstring中,返回值為
return nlen != 0;
也就是說,只要讀出的cstring的長度不為0就為讀取成功。與我修改後的方法完全一致。就這樣向客戶解釋,然後修改了。悲劇的是,幾年前所有程式中所有使用readstring函式的地方,都要進行修改。。。
mfc的這個bug比較隱蔽,平常不容易發現,但一旦遇到特殊長度的資料,就會表現異常。所以,在用vc6.0開發的時候,盡量避免使用readstring,或者在使用中,多判斷一步讀取出來的cstring長度。避開這個bug。
MFC中乙個危險的Bug
mfc中乙個危險的bug 上次說日本海嘯警報的時候,程式出錯。在解析 的時候,發現了mfc中的乙個bug。一。問題的產生。這個程式,用來處理日本各種天氣預報資料,包括災害的預報。如果 颱風之類的自然災害到來,程式會把預報資料進行處理,生成相應的警報資訊,並在電視上面顯示滾動的字幕來提示。程式本身,是...
快排中乙個巨集的bug
今天寫快速排序,碰到乙個問題 使用了下面的巨集 define swap a,b int temp a a b b temp 咋看沒有問題,但是在使用時a,不是乙個值而是乙個表示式 swap v m v i 這樣在編譯器預處理的時候會展開為 int temp v m v m v i v i temp ...
由框架中乙個BUG引起的
今天加班在給new guys做培訓的時候,發生了乙個莫名其妙的問題,幾經周折,才發現是公司現有框架的乙個bug。xml converter valueobject objectid lib class com.icsc.tm.mscdao.tmjcs03vo type unique converte...