CString对象的一种错误的使用方式

类别:VC语言 点击:0 评论:0 推荐:
我现在做的系统有的时候会出现这样的断言失败:Debug Error! DAMAGE: after Normal block (#3289) 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 stringASSERT(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 (#3289) at 0x182C30F0.的错误,应想到内存冲突。 问题终于水落石出了。反思一下,这个问题一点也不难,都怪自己基础没有打好,考虑问题不周全。

本文地址:http://com.8s8s.com/it/it33.htm