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

我现在做的系统有的时候会出现这样的断言失败:

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! ");

#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. ",

                    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.

的错误,应想到内存冲突。

 

问题终于水落石出了。反思一下,这个问题一点也不难,都怪自己基础没有打好,考虑问题不周全。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值