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