最近測驗發現一個掩藏很深的bug,這個只有在特定的操作場景下才會出現,和測驗資料及場景有強相關性,好在我們找到了復現的辦法,直接在Debug除錯下復現了問題,復現問題后,彈出了如下的報錯提示框:

根據提示資訊,檢測到了堆記憶體被破壞,程式向堆記憶體前面的記憶體區域寫入了內容,系統在分配堆記憶體時,會在給用戶使用的堆記憶體前后加上頭資訊和尾資訊,用來維護和管理這些堆記憶體,這些堆記憶體的頭部記憶體和尾部記憶體區域,應用程式是不能寫入的,是系統來維護和管理的,
本例是堆記憶體越界到記憶體的頭部記憶體區域,而一般情況是越界到記憶體尾部的,越界到頭部則是比較少見的,當然這不排除記憶體越界的比較長,直接越到另一段堆記憶體的頭部記憶體區域的,
VS檢測到例外后中斷下來,發現是崩潰在對堆記憶體的delete釋放代碼上:

按講釋放一段申請的堆記憶體是不應該有問題的,如果在new或者delete時報錯,排除對堆記憶體delete兩次的場景,那基本可以斷定是堆記憶體被破壞了,導致new或delete出現例外,
至于到底是什么情況,需要去結合代碼去分析了,于是去查看出問題函式的代碼背景關系,查看相關變數的值,發現我們在new出一段堆記憶體后,使用陣列下標去訪問該段堆記憶體,結果在當前的場景下,陣列下標出現了-1的情況,對該-1下標對應的記憶體區域進行了賦值,所以就越界到了堆記憶體的頭部區域,然后被系統檢測到了,系統產生了例外,相關代碼如下所示:
void CRenderEngine::DrawText( HDC hDC, CPaintManagerUI* pManager, RECT& rc, LPCTSTR pstrText, DWORD dwTextColor, int iFont, UINT uStyle )
{
ASSERT(::GetObjectType(hDC) == OBJ_DC || ::GetObjectType(hDC) == OBJ_MEMDC);
if (pstrText == NULL || pManager == NULL) return;
::SetBkMode(hDC, TRANSPARENT);
::SetTextColor(hDC, RGB(GetBValue(dwTextColor), GetGValue(dwTextColor), GetRValue(dwTextColor)));
HFONT hOldFont = (HFONT)::SelectObject(hDC, pManager->GetFont(iFont));
// 檢查一下待繪制的字串中是否有換行符,如果有換行符,則不能使用下面處理省略號的演算法
// 解決SDM-00125710
bool bHasCarriageReturn = false;
CStdString strTmp = pstrText;
if ( strTmp.Find( _T("\n") ) != -1 )
{
bHasCarriageReturn = true;
}
else if ( strTmp.Find( _T("\r\n") ) != -1 )
{
bHasCarriageReturn = true;
}
if ( (uStyle & DT_WORDBREAK) || bHasCarriageReturn )
{
::DrawText(hDC, pstrText, -1, &rc, uStyle | DT_NOPREFIX );
}
else if( uStyle & (DT_END_ELLIPSIS | DT_MODIFYSTRING) )
{
// 為了解決endellipsis的省略號顯示不全問題:
// 1)有些情況下省略號的三個點顯示不全;
// 2)win7下省略號顯示正常,win10下不顯示省略號
// 現做如下處理,參考
// https://www.codeproject.com/articles/7079/implementing-the-dt-end-ellipsis-flag-on-the-pocke
// 注意:下面的代碼只能處理單行文字顯示時的結尾省略號的問題
int nCount = 0;
int nWidth = rc.right - rc.left;
SIZE szStr = { 0, 0 };
LPTSTR lpStr = NULL;
// 計算需繪制的字串字符個數
nCount = _tcslen(pstrText);
if( nCount == 0 || nCount == -1 )
{
// 0是沒有字符需要繪制,直接回傳,-1是計算字串長度出錯
::SelectObject(hDC, hOldFont);
return;
}
// 復制一份文字內容
lpStr = new TCHAR[nCount+1];
ZeroMemory( lpStr , (nCount+1)*sizeof(TCHAR) );
_tcscpy_s( lpStr, nCount+1, pstrText );
lpStr[nCount] = _T('\0');
// 檢查如果字串長度大于繪制區域寬度
GetTextExtentPoint32( hDC, lpStr, nCount, &szStr );
if( szStr.cx > nWidth && nWidth > 0 )
{
// 估算出繪制區域能夠顯示的字符數量
// 估算公式:nWidth/nEstimate = szStr.cx/nCount
// 該公式假定字串中的字符寬度是相等的,實際上數字或字母與中文的寬度是不同的,此處
// 只是粗略的估計
int nEstimate = (nCount * nWidth) / szStr.cx + 1;
if(nEstimate < nCount)
nCount = nEstimate;
const DWORD dwEllipsisUnicode = 0x2026;
// 0x2026對應省略號三個點的Unicode編碼
lpStr[nCount-1] = (TCHAR)dwEllipsisUnicode;
GetTextExtentPoint32( hDC, lpStr, nCount, &szStr );
// 當延伸的內容大于繪制區域的寬度,移除這個字符
while(szStr.cx > nWidth && nCount > 1)
{
lpStr[--nCount] = 0; // 移除字符
lpStr[nCount-1] = (TCHAR)dwEllipsisUnicode; // 用...代替
GetTextExtentPoint32(hDC, lpStr, nCount, &szStr);
}
::DrawText(hDC, lpStr, nCount, &rc, uStyle | DT_NOPREFIX );
delete[] lpStr;
lpStr = NULL;
}
else
{
::DrawText(hDC, pstrText, -1, &rc, uStyle | DT_NOPREFIX );
}
}
else
{
::DrawText(hDC, pstrText, -1, &rc, uStyle | DT_NOPREFIX );
}
::SelectObject(hDC, hOldFont);
}
我們一定要注意,在使用運算式作為陣列下標去訪問記憶體時,一定要注意對陣列下標值做保護,放置出現例外的值,比如負值或者超過陣列長度的值,我們在此處添加一個保護即可,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/377147.html
標籤:其他
