CString对象的一种错误的使用方式
生活随笔
收集整理的這篇文章主要介紹了
CString对象的一种错误的使用方式
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
我現在做的系統有的時候會出現這樣的斷言失敗:
Debug Error!
DAMAGE: after Normal block (#328Array) at 0x182C30F0.
跟蹤一下,發現問題竟出在CString的析構函數中,于是拿出了大半天的時間來研究這個問題,終于發現了原因所在。
問題的起因是我像下面這樣調用無參的構造函數聲明一個CString對象:
CString strText;
然后把它以這樣的方式傳遞給別的函數:(函數1)
pVCG->GetRotDirection(WAVE_P, m_nWaveSide, strText.GetBuffer(0));
而在這個函數里對于字符串指針進行了類似于如下的操作:
sprintf(strDir, "%s", "CW");
這樣做的危險性在于當字符串沒有被初始化的時候,CString內部指向緩沖區的指針指向的是一個隨機的地址,在CString的無參構造函數調用
了如下函數:
_AFX_INLINE void CString::Init()
{ m_pchData = afxEmptyString.m_pchData; }
m_pdhData的定義:LPTSTR m_pchData;
afxEmptyString的定義是:
#define afxEmptyString AfxGetEmptyString()
const CString& AFXAPI AfxGetEmptyString()
{ return *(CString*)&_afxPchNil; }
_afxPchNil的來源如下:
AFX_STATIC_DATA int _afxInitData[] = { -1, 0, 0, 0 };
AFX_STATIC_DATA CStringData* _afxDataNil = (CStringData*)&_afxInitData;
AFX_COMDAT LPCTSTR _afxPchNil = (LPCTSTR)(((BYTE*)&_afxInitData)+sizeof(CStringData));
從上面的代碼可以看出,沒有進行初始化操的CString對象它們的緩沖區指針都是指向一塊相同的內存:和一個全局數組相關的地址。
而在函數1例調用sprintf修改CString對象的緩沖區的結果是修改所有未初始化CString內部緩沖區指針所指,這么做是非常危險的。但是這還不是出現斷言錯誤的原因。
接下來的錯誤,更難被發現。接著我的程序又調用了兩次類似于下面的函數(函數2)
pVCG->GetCompressionGrade(WAVE_QRS, m_nWaveSide, 0, 60, 0, 0, strText);
在這個函數的內部有str.Format(IDS_COMPRESSION_LESS);這樣的操作。
這是MFC里CString::Format的相關代碼:
void AFX_CDECL CString::Format(UINT nFormatID, ...)
{
CString strFormat;//沒有直接修改自己,而是先對新聲明的字符串進行操作
VERIFY(strFormat.LoadString(nFormatID) != 0);
?
va_list argList;
va_start(argList, nFormatID);
FormatV(strFormat, argList);
va_end(argList);
}
而在void CString::FormatV(LPCTSTR lpszFormat, va_list argList)里最后作如下操作:
GetBuffer(nMaxLen);
VERIFY(_vstprintf(m_pchData, lpszFormat, argListSave) <= GetAllocLength());//將修改后的字符串拷貝到自己的緩沖區內
ReleaseBuffer();
關鍵在GetBuffer:
LPTSTR CString::GetBuffer(int nMinBufLength)
{
ASSERT(nMinBufLength >= 0);
?
if (GetData()->nRefs > 1 || nMinBufLength > GetData()->nAllocLength)
//如果指定的內存空間比已經分配的空間小的話,則重新分配,并釋放掉原來的內存
{
#ifdef _DEBUG
??????? // give a warning in case locked string becomes unlocked
??????? if (GetData() != _afxDataNil && GetData()->nRefs < 0)
??????? ?????? TRACE0("Warning: GetBuffer on locked CString creates unlocked CString!\n");
#endif
// we have to grow the buffer
?????? CStringData* pOldData = GetData();
??????? int nOldLen = GetData()->nDataLength;?? // AllocBuffer will tromp it
??????? if (nMinBufLength < nOldLen)
??????? ?????? nMinBufLength = nOldLen;
?????? AllocBuffer(nMinBufLength);
?????? memcpy(m_pchData, pOldData->data(), (nOldLen+1)*sizeof(TCHAR));
?????? GetData()->nDataLength = nOldLen;
?????? CString::Release(pOldData);
}
ASSERT(GetData()->nRefs <= 1);
?
// return a pointer to the character storage for this string
ASSERT(m_pchData != NULL);
return m_pchData;
}
由于字符串沒有被初始化,所以GetData()->nAllocLength=0,因此if語句塊被執行,重新在堆上分配內存,銷毀原來的內存,這才第一次給字
符串分配內存。
這時還不會出現問題,接下來還會執行類似函數1的操作。
最后問題之所以發生在CString被析構的時候,原因就在于,在執行函數2的時候,字符串有了能容納4個字節的緩沖區.如果調試的時候打開Memory窗口,在Address:文本框里輸入一個堆內存的地址,可以發現VC在調試版的程序里為每個在堆里分配的內存塊的后面加了4個字節的內容,值全為FD,用于檢查內存越界。CString析構的時候,調用了調試版的operator delete,它就以此為依據進行了內存檢測:
if (!CheckBytes(pbData(pHead) + pHead->nDataSize, _bNoMansLandFill, nNoMansLandSize))
_RPT3(_CRT_ERROR, "DAMAGE: after %hs block (#%d) at 0x%08X.\n",
??????????????????? szBlockUseName[_BLOCK_TYPE(pHead->nBlockUse)],
??????????????????? pHead->lRequest,
??????????????????? (BYTE *) pbData(pHead));
由于后來再次調用的函數1時它產生的長度有的時候會大于4 ,就破壞了后面的邊界,所以會出現這樣的問題。
出現這種問題時,在調試狀態下會在輸出窗口輸出如下類似信息:
memory check error at 0x182C7F22 = 0x57, should be 0xFD
結論:
1.所以str.GetBuffer(0)作為參數傳遞的時候適合于作為只讀的參數;
2.如果非得要做可以修改的參數,那就得給GetBuffer傳遞一個保證足夠安全的參數,也就是足夠大;
2.如果調試版的程序出現類似
Debug Error!
DAMAGE: after Normal block (#328Array) at 0x182C30F0.
的錯誤,應想到內存沖突。
問題終于水落石出了。反思一下,這個問題一點也不難,都怪自己基礎沒有打好,考慮問題不周全。
總結
以上是生活随笔為你收集整理的CString对象的一种错误的使用方式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: KMP 算法并非字符串查找的优化 [转]
- 下一篇: 如何防止android软件被反编译,破解