??xml version="1.0" encoding="utf-8" standalone="yes"?>
上次说日本v啸警报的时候,E序出错。在解析代码的时候,发现了MFC中的一个Bug?/p>
一。问题的产生?/p>
q个E序Q用来处理日本各U天气预报数据,包括灑֮的预报。如果地震,台风之类的自然灾宛_来,E序会把预报数据q行处理Q生成相应的警报信息Qƈ在电视上面显C滚动的字幕来提C。程序本w,是几q前公司的其他h写的。里面有涉及到文件读写的地方Q有很多地方Q用了MFC中自带的文gdcCStdioFile?/p>
CStdioFileq个文gdc,估计大家都不陌生。这个类的父c,是CFilecRCStdioFilecLw的功能也很单。CStdioFilecL一个成员函数是ReadStringQ函数的定义如下Q?/p>
MSDN定义如下http://msdn.microsoft.com/library/x5t0zfyf(VS.80).aspxQ?pre style="border-bottom: #cecece 1px solid; border-left: #cecece 1px solid; padding-bottom: 5px; background-color: #fbfbfb; min-height: 40px; padding-left: 5px; width: 650px; padding-right: 5px; height: 118px; overflow: auto; border-top: #cecece 1px solid; border-right: #cecece 1px solid; padding-top: 5px">virtual LPTSTR ReadString(__out_ecount_z(nMax) LPTSTR lpsz, __in UINT nMax);
virtual BOOL ReadString(CString& rString);
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; or if boolean, FALSE if end-of-file was reached without reading any data.
ReadString函数能直接读取文本中的一行数据到CString中,很方ѝ读到文件结,没有dM数据的时候,q回FALSE。很单的函数Q但恰恰是这个函数有Bug?/p>
E序在处理数据的时候,会生成一些时文Ӟ然后会读取这些时文件中的数据,d操作Q正是用的CStdioFile的ReadString函数。读取流E很单:
while(dFile.ReadString(Str_temp))
{doSomething();}
当时的现象ؓQ读取到最后一行,L直接q回FALSEQ怎么也读不出最后一行来。看了看文g的最后一行,包含2176个字W的数据Q没有换行符。没有Q何异常啊。当时没惛_是MFC的BugQ因Z前有q样那样的毛病,多数是预报数据本w有问题Q所以这ơ也是先分析数据了。分析来分析去,没发现这ơ的数据有什么异常。后来发现如果最后一行的文g不是2176个字W,p正常d来。奇了怪了Q?176也不是什么特D长度啊。实验了几次后,觉的是在不对劌Ӏ莫非是MFC的BugQ?/p>
二。发现问题所?/p>
军_看看MFC的代码再说。做了个单的试E序Q跟到MFC代码里一看,果然是MFC的问题!试代码如下Q?/p>
CStdioFile dFile;dFile.Open("text.txt",CFile::modeRead);
CString str;while (dFile.ReadString(str) != FALSE )
{printf("%s", str);
}dFile.Close();
试代码很简单,读text.txt文g中的每一行,然后打印出来。还?176个字W就不行。确定了不是数据的问题,是MFC代码本n的Bug?/p>
MFC的ReadString代码如下Q(中文是我加的注释Q?/p>
BOOL CStdioFile::ReadString(CString& rString){ASSERT_VALID(this);
rString = &afxChNil; // empty string without deallocating
const int nMaxSize = 128; //临时字符串的长度LPTSTR lpsz = rString.GetBuffer(nMaxSize); //保存每次d到的字符串到CString?/span>
LPTSTR lpszResult; //指向每次d的字W串
int nLen = 0;
for (;;)
{lpszResult = _fgetts(lpsz, nMaxSize+1, m_pStream); //d操作
rString.ReleaseBuffer();// handle error/eof case
if (lpszResult == NULL && !feof(m_pStream))
{clearerr(m_pStream);AfxThrowFileException(CFileException::generic, _doserrno,m_strFileName);}// 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') // 最后结果中Q去掉回车符rString.GetBufferSetLength(nLen-1);return lpszResult != NULL; // q里是Bug的关键。返回g对!}
可以看到QReadString的底层,是用fgets来读取文件的。在内部Q每ơ读?28个字W到CString中,然后位置后移Q反复读?28个字W,直到遇到回RW或者文件结束。最后把回RW去掉,q回一个CString。其中,lpszResult也指向每ơ读出的字符丌Ӏ?/p>
q里q出问题所在了Q?176个字W,正好?28?7倍!也就是说Q?font color="#ff0000">只要文g最后一行是128倍数个字W,׃定会q回FALSE?/strong>
Z么会q样呢,因ؓReadString在每ơ读?28个字W的时候,用lpszResult指向d到的字符丌Ӏ如果读满了128个字W,ql读Q如果读到的字符不够128个,那么q束读取?/font>
当一行数据正好ؓ128的倍数Q又没有回RW的时候,会发生什么呢Q比如最后一行数据是128个,那么Q读一?28个字W,会l读下一ơ,但是下一ơ的dQ什么也没有dQlpszResult指向NULLQ最后的q回|是return lpszResult != NULL; 所以返回FALSE?/font>
但之前读到的128个字W,已经在CString里面了?/font>也就是说实际上读取已l成功了Q但q是q回了FALSE。返回g恰当Q?/font>
Bug的描qͼ当文件的最后一行数据,正好?28的倍数个字W的时候,?/font>ReadStringdQ一定会q回FALSE。但实际上读取是成功的,q回的CString中的数据是正的Q(VC6.0中存在这个BugQVS2005中,没有q个BugQ?/font>
q个BugQ只会媄响到最后一行数据。因为如果有换行W的存在QlpszResult׃会ؓNULL?/font>
三。解x?/font>
要解册个问题,也简单,修改一下判断ReadString成功与否的语句:
while (dFile.ReadString(str) != FALSE || str.GetLength() != 0)
在返回FALSE的情况下QCString的长度不?Q就不算dp|。或者这?
if(!dFile.ReadString(str) && str.GetLength() == 0)
在返回FALSEq且CString的长度ؓ0Q则读取失败,否则是d成功?/p>
q个E序Q是用VC6.0做的Q我有看了看VC2005中的代码Q发现这个Bug被修复了Q代码如下:
BOOL CStdioFile::ReadString(CString& rString){ASSERT_VALID(this);
rString = _T(""); // empty string without deallocating
const int nMaxSize = 128;LPTSTR lpsz = rString.GetBuffer(nMaxSize);LPTSTR lpszResult;int nLen = 0;
for (;;)
{lpszResult = _fgetts(lpsz, nMaxSize+1, m_pStream);rString.ReleaseBuffer();// handle error/eof case
if (lpszResult == NULL && !feof(m_pStream))
{Afx_clearerr_s(m_pStream);AfxThrowFileException(CFileException::genericException, _doserrno,m_strFileName);}// 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; //q回值变了!}
我们看到QVC2005中,d部分的代码与VC6.0中的代码完全一栗不一L地方只是q回值的部分。VC2005的ReadString中,q回gؓ
return nLen != 0;
也就是说Q只要读出的CString的长度不?׃ؓd成功。与我修改后的方法完全一致。就q样向客戯释,然后修改了。悲剧的是,几年前所有程序中所有用ReadString函数的地方,都要q行修改。。?/p>
MFC的这个Bug比较隐蔽Q^怸Ҏ发现Q但一旦遇到特D长度的数据Q就会表现异常。所以,在用VC6.0开发的时候,量避免使用ReadStringQ或者在使用中,多判断一步读取出来的CString长度。避开q个Bug?/p>
1960q智利v域发生了9.5U(太恐怖了。。。)地震。引起了啸Q一直穿q整个太qxQ从南美Q一直到东亚。日本,夏威P菲律N?00多hM。所以,q次8.8U地震,如果再来一ơv啸,那可不得了啊。这ơ智利爆发的地震Q引发了剧烈的v啸。如今,啸的巨正在横q太qxQ直奔日本v岸而来。估计到达日本时Q浪高依然可以达?0-20英尺?/p>
日本全国都在紧急动员防范v啸,电视台在电视屏幕一角实时展CZq日本地图,所有专安会遭到啸袭击的地区都被标记出来,如今Q从北v?到冲lI整个日本东v岸几乎都变成了一片红艌Ӏ屏q上方则在滚动播出沿各地发布的遉K通知Q例如,青森县已l有一万九千多戯要求dq入公用遉K?施。日本全国到现在为止已有40万hd家园。整个流E顺畅,井然有序。这一切,一斚w反映了日本在自然灑֮面前的准备工作,另一斚w也反映了日本?间的恐惧?/p>
q播员在不断在播报各地vq面的增高情况,镜头不时切换到沿岸各圎ͼ报道当地状况和抢险准备的情况Q很多沿公路如东名高速公路已l关闭,h公园停止营业Q船舶纷U入避难\Uѝ经常有dx断播韛_的播报,紧急通报某地^面出现异常增高。{眼间有了一U陷入某场战争的感觉?/p>
发个图,看的比较清楚。整个日本靠太^z的一边,全部是v啸警报?/p>
公司做的目Q正好是l各个电视台做的天气预报目Q地震台风v啸的预报Q也包含在内。关键时刻,日本电视台打电话_预报图显C的有误Q只能看到大阪的高Q其他地Ҏ有浪高。导致只能显C部分的警报图。东京电视台也打电话说电视上滚动昄的警报文字,用我们的E序处理不了Q显C出不来。马上ؕ套了Q当天公司几个h都通宵在处理。最后强制显C全国的警报图?/p>
最后发玎ͼ日本电视台预报图昄有误Q其实是正确的,本来其他地方没有浪高。ؓ什么呢Q因为根本就没那么大的浪Q?/p>
日本气象厅在3??0?5分全面解除太qx沿岸啸警报?
气象厅负责地震v啸检的N在记者会见中谢罪Uͼ“对于啸预测大大过了实际情况,以及警报旉q长表示歉意?#8221;
Z么这ơ的地震Q没有引起很大的啸呢?
智利此次地震所引发的v怹h很强的方向性,英国威尔士大学新港学院(University of Wales, NewportQ的Simon Haslett ?#8220;q回的v啔R常有方向性,而不是U均匀向四周传播的‘往池塘里扔矛_’似的波浪”。他表示震中最q的岸Q以及胡安费南h岛QJuan Fernandez IslandsQv啔R常强Q但是其他方向的啸能量和高度迅速减退?
而且Q地震震源的相对深度?5公里—可能也减小了v床的上升Q而正是v床的上升排挤了v水。英国u敦大学学院Bill McGuire表示“相比?004q的印度z地震,智利地震要更深,释放到地表的能量也更?#8221;?
虚惊一场啊?
至于东京电视台的预报文字处理不出来,l过我一步步DebugQ最后发玎ͼ是MFC的一个Bug造成的!气死我了。导致所有代码涉及这个Bug的地斚w要修攏V下日志,详细说说q个Bug?/p>