ATL2.1版的CString分析
2011-06-12 07:35 menggucaoyuan 阅读(1374) 评论(0) 编辑 收藏 举报介绍CString类之前先介绍下结构体CStringData和几个全局变量。
结构体CStringData保存了CString类的空间大小和引用信息,相当于信息头,而CString的m_pchData指向了实际数据区域。如果多个字符串的数据一样,可以让他们的对象引用一个数据空间,CString类就是这样实现的,其中的信息头就保存了引用次数。当然使用CString类的人不看源码的话,不会知道这些事实,他们会感觉使用两个不同的数据空间。
用户每次访问CString的某段数据时,程序就需要根据CStringData保存的信息头内容先确定这段数据的存在性。其源码如下:
struct CStringData
{
long nRefs; // reference count //被引用次数,实际上多份CString的指针可能指向同一个内存空间
int nDataLength; //已占用的内存空间大小
int nAllocLength; //实际分配的内存空间大小
// TCHAR data[nAllocLength]
TCHAR* data() //信息头后面的就是数据
{ return (TCHAR*)(this+1); } //等效于(BYTE*)rgInitData + 12
};
全局数据、全局变量和全局函数如下:
// afxChNil is left for backward compatibility
_declspec(selectany) TCHAR afxChNil = '\0'; //代表CString为NULL时的情形
//_declspec(selectany)
// For an empty string, m_pchData will point here
// (note: avoids special case of checking for NULL m_pchData)
// empty string data (and locked)
_declspec(selectany) int rgInitData[] = { -1, 0, 0, 0 };
_declspec(selectany) CStringData* afxDataNil = (CStringData*)&rgInitData;
_declspec(selectany) LPCTSTR afxPchNil = (LPCTSTR)(((BYTE*)&rgInitData)+sizeof(CStringData));
inline const CString& __stdcall AfxGetEmptyString()
{ return *(CString*)&afxPchNil; }
#define afxEmptyString AfxGetEmptyString()
以上代码中,最重要的是数组rgInitData。当一个CString的实例初始化时没有分配内存空间时,它会引用这段内存空间,或者一个CString实例的内存空间被释放时,即调用Release函数后,它也会再次引用这段内存空间。所以一个CString实例引用这段内存空间的话,基本上可以断定它是一个空的实例。rgInitData的前三个元素保存了CString的内存空间区域信息,这三个元素占用的内存空间大小与CStringData的大小一致,所以它们实际上构成了结构体CStringData的一个对象, rgInitData[0]等效于nRefs,而rgInitData[1]相当于nDataLength,则rgInitData[2]就是nAllocLength了。
下面开始逐步介绍CString一些有用的函数的源码:
1 CString实例的初始化时的内存空间的分配以及被删除时其内存空间的释放
下面介绍CString类的初始过程。如果CString初始化时,被指定的长度为零,则它的指针就会指向rgInitData[3],以作为初始空间。这个功能是有函数Init完成,它会被AllocBuffer调用。Init代码如下:
inline void CString::Init()
{ m_pchData = afxEmptyString.m_pchData; }
inline CString::CString()
{
Init();
}
CString在第一次初始化的时候,一般要先分配一定大小的空间,这个功能是由函数AllocBuffer完成的,代码如下:
inline BOOL CString::AllocBuffer(int nLen)
// always allocate one extra character for '\0' termination
// assumes [optimistically] that data length will equal allocation length
{
ATLASSERT(nLen >= 0);
ATLASSERT(nLen <= INT_MAX-1); // max size (enough room for 1 extra)
if (nLen == 0)
Init();
else
{
UINT SizeToAlloc = nLen;
CStringData* pData = NULL;
if (SizeToAlloc > SizeToAlloc+1) {//类似的无用代码很多,不明白M$写这种代码是干嘛用
return FALSE;
}
SizeToAlloc++; //加一,最后一个空间分配给结尾符号'\0'
if (SizeToAlloc > (SizeToAlloc * sizeof(TCHAR))) {
return FALSE;
}
SizeToAlloc *= sizeof(TCHAR); //实际要分配的内存空间的大小
//最后加上自己的信息头空间
if (SizeToAlloc > (SizeToAlloc + sizeof(CStringData))) {
return FALSE;
}
SizeToAlloc += sizeof(CStringData);
ATLTRY(pData = (CStringData*)new BYTE[SizeToAlloc]);
if(pData == NULL)
return FALSE;
//注意以下四行代码,CString有自己的数据空间,就要有自己的信息头,不再使用全局的rgInitData
pData->nRefs = 1;
pData->data()[nLen] = '\0';
pData->nDataLength = nLen;
pData->nAllocLength = nLen;
m_pchData = pData->data();
}
return TRUE;
}
CString的一个实例的空间分配完成后,如果别的实例要拷贝这个实例的数据,CString类会让下一个实例引用这个实例。
inline CString::CString(const CString& stringSrc)
{
ATLASSERT(stringSrc.GetData()->nRefs != 0);
if (stringSrc.GetData()->nRefs >= 0)
{
//stringSrc引用this
ATLASSERT(stringSrc.GetData() != afxDataNil);
m_pchData = stringSrc.m_pchData;
InterlockedIncrement(&GetData()->nRefs);
}
else
{
Init();
*this = stringSrc.m_pchData;
}
}
inline const CString& CString::operator=(const CString& stringSrc)
{
if (m_pchData != stringSrc.m_pchData) //防止自拷贝
{
//如果目标对象的数据空间已经分配,而且没有别的CString实例指向它,则把stringSrc的数据拷贝过来;
//或者,如果stringSrc的数据的引用次数小于零,则把它的数据拷贝过来
if ((GetData()->nRefs < 0 && GetData() != afxDataNil) ||
stringSrc.GetData()->nRefs < 0)
{
// actual copy necessary since one of the strings is locked
AssignCopy(stringSrc.GetData()->nDataLength, stringSrc.m_pchData);
}
else
{
//其他情况,就先自己的数据空间释放掉,然后再指向stringSrc的数据空间
// can just copy references around
Release();
ATLASSERT(stringSrc.GetData() != afxDataNil);
m_pchData = stringSrc.m_pchData;
InterlockedIncrement(&GetData()->nRefs); //引用次数增1,这里为了防止多线程情况下出错,使用了原子性自增
}
}
return *this;
}
inline void CString::AssignCopy(int nSrcLen, LPCTSTR lpszSrcData)
{
if(AllocBeforeWrite(nSrcLen))
{
memcpy(m_pchData, lpszSrcData, nSrcLen*sizeof(TCHAR));
GetData()->nDataLength = nSrcLen;
m_pchData[nSrcLen] = '\0';
}
}
上面分析了CString数据空间的分配,下面的Release()函数则完成了对这些数据空间的释放。
inline void CString::Release()
{
if (GetData() != afxDataNil) //确保不会释放全局的rgInitData
{ //释放空间的条件是没有CString实例指向这个数据空间
ATLASSERT(GetData()->nRefs != 0);
if (InterlockedDecrement(&GetData()->nRefs) <= 0) //注意,在此执行了引用次数减1操作
delete[] (BYTE*)GetData();
Init(); //数据释放后,让CString对象重新指向全局的数据空间rgInitData
}
}
一般释放CString实例的内存空间的时候,可以使用函数Empty()释放内存空间,不要调用Release()函数,因为Empty()本身会调用Release()函数释放内存空间,然后会做一些"善后处理"工作。
inline void CString::Empty()
{
if (GetData()->nDataLength == 0)
return;
if (GetData()->nRefs >= 0)
Release();
else
*this = &afxChNil;
ATLASSERT(GetData()->nDataLength == 0);
ATLASSERT(GetData()->nRefs < 0 || GetData()->nAllocLength == 0);
}
inline void PASCAL CString::Release(CStringData* pData)
{
if (pData != afxDataNil) //确保不会释放全局的内存空间
{
ATLASSERT(pData->nRefs != 0);
if (InterlockedDecrement(&pData->nRefs) <= 0) //没有实例引用这段内存,则释放之
delete[] (BYTE*)pData;
}
}
还记得CStingData的两个成员nAllocLength和nDataLength两个成员变量吗?nDataLength是已使用的存放了数据的内存空间的大小,而nAllocLength则存放了为这个实例预分配的内存空间的大小。如果nAllocLength大于nDataLength,而且你想释放这点额外的空间,则可以通过函数FreeExtra实现。
inline void CString::FreeExtra()
{
ATLASSERT(GetData()->nDataLength <= GetData()->nAllocLength);
if (GetData()->nDataLength != GetData()->nAllocLength)
{
CStringData* pOldData = GetData();
if(AllocBuffer(GetData()->nDataLength))
{
memcpy(m_pchData, pOldData->data(), pOldData->nDataLength*sizeof(TCHAR));
ATLASSERT(m_pchData[GetData()->nDataLength] == '\0');
CString::Release(pOldData);
}
}
ATLASSERT(GetData() != NULL);
}
2 CString实例的内存数据的调用
在使用CString类时,常用的函数之一是GetBuffer,以获取CString的数据。经常会建议你在使用这个函数后,紧跟着调用ReleaseBuffer函数,以免造成内存泄露。下面看下这两个函数的源码:
inline LPTSTR CString::GetBuffer(int nMinBufLength)
{
ATLASSERT(nMinBufLength >= 0);
//从以下if条件来看,如果以GetBuffer()形式调用,则不会引发新的内存空间的申请与数据拷贝
if (GetData()->nRefs > 1 || nMinBufLength > GetData()->nAllocLength)
{
// we have to grow the buffer
CStringData* pOldData = GetData();
int nOldLen = GetData()->nDataLength; // AllocBuffer will tromp it
if (nMinBufLength < nOldLen)
nMinBufLength = nOldLen; //保证存储空间的最小值,保证数据不丢失
if(AllocBuffer(nMinBufLength)) //分配新的空间
{
memcpy(m_pchData, pOldData->data(), (nOldLen+1)*sizeof(TCHAR)); //数据拷贝
GetData()->nDataLength = nOldLen;
CString::Release(pOldData); //释放旧有的数据空间
}
}
ATLASSERT(GetData()->nRefs <= 1);
// return a pointer to the character storage for this string
ATLASSERT(m_pchData != NULL);
return m_pchData;
}
如果用户是以GetBuffer(nLen)形式调用而且nLen 小于CString实例的原有字符串的长度,下面的这个函数就显得很有必要了。
inline void CString::ReleaseBuffer(int nNewLength)
{
CopyBeforeWrite(); // just in case GetBuffer was not called
if (nNewLength == -1)
nNewLength = lstrlen(m_pchData); // zero terminated
ATLASSERT(nNewLength <= GetData()->nAllocLength);
GetData()->nDataLength = nNewLength;
m_pchData[nNewLength] = '\0'; //指定新的正确的结尾
}
另一个比较重要的函数是GetData(), 它的返回值是CString的信息头,即存储CStringData的那一部分。
inline CStringData* CString::GetData() const
{ ATLASSERT(m_pchData != NULL); return ((CStringData*)m_pchData)-1; }
3 CString实例的数据保护
由于CString允许多个实例的数据存储在同一个内存空间,所以必定存在多个实例同时访问一段内存空间的情况,为了防止“读时写”发生写出了"脏"数据的情况,可以使用写前拷贝技术防止这种情况发生。CString是通过CopyBeforeWrite()函数实现的,一般在CString的一个实例要改变其内存空间的数据时,它会检查已有的内存空间的引用次数是否大于1,如果大于1则先把已有的数据通过复制保护起来,然后再为这个CString的实例分配新的内存空间。
inline void CString::CopyBeforeWrite()
{
if (GetData()->nRefs > 1)
{
CStringData* pData = GetData();
Release(); //引用次数减1,即这个CString实例(this)不再引用这段内存空间的数据
if(AllocBuffer(pData->nDataLength))
memcpy(m_pchData, pData->data(), (pData->nDataLength+1)*sizeof(TCHAR)); //把旧有的数据拷贝过来
}
ATLASSERT(GetData()->nRefs <= 1);
}
类似的还有AllocBeforeWrite,比较类似于CopyBeforeWrite,这个函数为Cstring的实例分配空间后,不会复制已有的内存的数据。
inline BOOL CString::AllocBeforeWrite(int nLen)
{
BOOL bRet = TRUE;
if (GetData()->nRefs > 1 || nLen > GetData()->nAllocLength)
{
Release();
bRet = AllocBuffer(nLen);
}
ATLASSERT(GetData()->nRefs <= 1);
return bRet;
}
CString还提供了一对加锁函数LockBuffer/ReleaseBuffer,它们分别可以把对数据的引用次数设置为-1和设置为1。
inline LPTSTR CString::LockBuffer()
{
LPTSTR lpsz = GetBuffer(0);
GetData()->nRefs = -1;
return lpsz;
}
inline void CString::UnlockBuffer()
{
ATLASSERT(GetData()->nRefs == -1);
if (GetData() != afxDataNil)
GetData()->nRefs = 1;
}
如果出现以下代码:
CString str1(_T("hello"));
CString str2();
str1.LockBuffer();
str2 = str1; //此时str2不会引用str1的内存空间,operator =()函数为str2开辟新的内存空间,然后拷贝str1的数据