从内存构造开始说起
我们定义一个CString变量,虽然我们工程用的是Unicode设置。但是”abc”是ANSI字符串。因为我们没有加_T限制啊。
CString str("abc");
由此宏
typedef ATL::CStringT< TCHAR, StrTraitMFC< TCHAR > > CString;
CSTRING_EXPLICIT CStringT(_In_opt_z_ const YCHAR* pszSrc) :
CThisSimpleString( StringTraits::GetDefaultManager() )
{
if( !CheckImplicitLoad( pszSrc ) )
{
*this = pszSrc;
}
}
StringTraits就是上面的的那个StrTraitMFC模板类。
static ATL::IAtlStringMgr* GetDefaultManager() throw()
{
return( AfxGetStringManager() );
}
IAtlStringMgr* AFXAPI AfxGetStringManager()
{
return &afxStringManager;
}
这个afxStringManager是一个全局变量。对,每个CString居然对应同一个字符串管理器。
CAfxStringMgr afxStringManager;
下面进入CSTringT的父类CSimpleStringT这个构造函数。
explicit CSimpleStringT(_Inout_ IAtlStringMgr* pStringMgr)
{
ATLENSURE( pStringMgr != NULL );
CStringData* pData = pStringMgr->GetNilString();
Attach( pData );
}
这个pStringMgr指向刚才那个全局的afxStringManager。
CStringData* CAfxStringMgr::GetNilString() throw()
{
m_nil.AddRef();
return &m_nil;
}
m_nil是一个全局变量afxStringManager的成员变量,那么它也是全局的喽。
在这里面m_nil的成员变量+1,表示有多少个CSTring变量用到了此CStringData,即m_nil。
void AddRef() throw()
{
ATLASSERT(nRefs > 0);
AtlInterlockedIncrement(&nRefs);
}
再把此CStringData关联到我们的CSimpleStringT。
void Attach(_Inout_ CStringData* pData) throw()
{
m_pszData = static_cast< PXSTR >( pData->data() );
}
CSimpleStringT类中定义了成员变量:PXSTR m_pszData;在Unicode环境下,但我们传的是ANSI字符,所以PXSTR应该是char*类型。
看看的CStringData的data函数,返回的是一个sizeof( CStringData )大小后面的内存。那一块内存放着我们的字符串。那岂不是现在有N个CString变量共享这块内存?是的,后面继续看。
void* data() throw()
{
return (this+1);
}
返回到StringT的构造函数,来看这一句,*this = pszSrc;然后进入赋值构造函数:
CStringT& operator=(_In_opt_z_ PCYSTR pszSrc)
{
// nDestLength is in XCHARs
int nDestLength = (pszSrc != NULL) ? StringTraits::GetBaseTypeLength( pszSrc ) : 0;
if( nDestLength > 0 )
{
PXSTR pszBuffer = GetBuffer( nDestLength );
StringTraits::ConvertToBaseType( pszBuffer, nDestLength, pszSrc);
ReleaseBufferSetLength( nDestLength );
}
else
{
Empty();
}
return( *this );
}
nDestLength ,就是多字节字符串转换为宽字符字符串后的字符数,比如多字节的”abc”转为宽字节后,nDestLength 的值为3。
static int __cdecl GetBaseTypeLength(_In_z_ LPCSTR pszSrc) throw()
{
// Returns required buffer size in wchar_ts
return ::MultiByteToWideChar( _AtlGetConversionACP(), 0, pszSrc, -1, NULL, 0 )-1;
}
然后看看CSimpleStringT的重量级成员函数吧:
_Ret_cap_(nMinBufferLength + 1) PXSTR GetBuffer(_In_ int nMinBufferLength)
{
return( PrepareWrite( nMinBufferLength ) );
}
PXSTR PrepareWrite(_In_ int nLength)
{
if (nLength < 0)
AtlThrow(E_INVALIDARG);
CStringData* pOldData = GetData();
int nShared = 1-pOldData->nRefs; // nShared < 0 means true, >= 0 means false
int nTooShort = pOldData->nAllocLength-nLength; // nTooShort < 0 means true, >= 0 means false
if( (nShared|nTooShort) < 0 ) // If either sign bit is set (i.e. either is less than zero), we need to copy data
{
PrepareWrite2( nLength );
}
return( m_pszData );
}
nRefs变量至少被现在我们的str变量AddRef了一次,所以至少为1,若是大于一,说明和其它一个变量share,但是share的话,只能共读,不能共写(共写的话岂不是一份内存要保存两份字符串,矛盾了)。而我们这个函数叫PrepareWrite,要Write了。只有nRefs==1,或<1(有<1这种情况吗?)并且nAllocLength>nLength(已分配内存大于要分配内存)的情况才不会进入PrepareWrite2。
ATL_NOINLINE void PrepareWrite2(_In_ int nLength)
{
CStringData* pOldData = GetData();
if( pOldData->nDataLength > nLength )
{
nLength = pOldData->nDataLength;
}
if( pOldData->IsShared() )
{
Fork( nLength );
}
else if( pOldData->nAllocLength < nLength )
{
// Grow exponentially, until we hit 1G, then by 1M thereafter.
int nNewLength = pOldData->nAllocLength;
if( nNewLength > 1024 * 1024 * 1024 )
{
nNewLength += 1024 * 1024;
}
else
{
// Exponential growth factor is 1.5.
nNewLength = nNewLength + nNewLength / 2;
}
if( nNewLength < nLength )
{
nNewLength = nLength;
}
Reallocate( nNewLength );
}
}
我们这边会进入Fork( nLength )这个分支。
ATL_NOINLINE void Fork(_In_ int nLength)
{
CStringData* pOldData = GetData();
int nOldLength = pOldData->nDataLength;
CStringData* pNewData = pOldData->pStringMgr->Clone()->Allocate( nLength, sizeof( XCHAR ) );
if( pNewData == NULL )
{
ThrowMemoryException();
}
int nCharsToCopy = ((nOldLength < nLength) ? nOldLength : nLength)+1; // Copy '\0'
CopyChars( PXSTR( pNewData->data() ), nCharsToCopy,PCXSTR( pOldData->data() ), nCharsToCopy );
pNewData->nDataLength = nOldLength;
pOldData->Release();
Attach( pNewData );
}
IAtlStringMgr* CAfxStringMgr::Clone() throw()
{
return this;
}
从这个Clone()就可以看出IAtlStringMgr还是之前全局的那个。
CStringData* CAfxStringMgr::Allocate( int nChars, int nCharSize ) throw()
{
size_t nTotalSize;
CStringData* pData;
size_t nDataBytes;
ASSERT(nCharSize > 0);
if(nChars < 0)
{
ASSERT(FALSE);
return NULL;
}
nDataBytes = (nChars+1)*nCharSize;
nTotalSize = sizeof( CStringData )+nDataBytes;
pData = (CStringData*)malloc( nTotalSize );
if (pData == NULL)
return NULL;
pData->pStringMgr = this;
pData->nRefs = 1;
pData->nAllocLength = nChars;
pData->nDataLength = 0;
return pData;
}
我们这边的”abc”是多字节,但是我们的工程是Unicode的,所以最终我们的字符以Unicode格式存储于内存。nCharSize 此处则为2。多分配了sizeof( CStringData ),以存放CStringData 信息,这里存放的不是一个CStringData 指针哟,而是sizeof( CStringData )大的内存,以存放一整个CStringData信息。不过要注意的一点是,nAllocLength 为字符数,而不是字节数。回到Fork函数:
为什么这个Copy '\0'要像代码中这么搞呢?不太理解,想想可能是因为现在的StringT与之前的StringT共享一块内存,现在要自立内存了,也要把之前的数据拷过来吧。
CStringData的Release()也比较简单,就是引用计数<=0就把内存释放掉。
void Release() throw()
{
ATLASSERT( nRefs != 0 );
if( _AtlInterlockedDecrement( &nRefs ) <= 0 )
{
pStringMgr->Free( this );
}
}
void CAfxStringMgr::Free( CStringData* pData ) throw()
{
free(pData);
}
现在回到CStringT的operator= 函数:
在Fork函数的Attach调用中已经将m_pszData指向了用于存放字符串的内存。GetBuffer函数返回的是m_pszData。
static void __cdecl ConvertToBaseType(
_Out_cap_(nDestLength) LPWSTR pszDest,
_In_ int nDestLength,
_In_ LPCSTR pszSrc,
_In_ int nSrcLength = -1) throw()
{
// nLen is in wchar_ts
::MultiByteToWideChar( _AtlGetConversionACP(), 0, pszSrc, nSrcLength, pszDest, nDestLength );
}
现在再把多字节的”abc”转换为宽字节的字符串并存到m_pszData中。再看看ReleaseBufferSetLength:
void ReleaseBufferSetLength(_In_ int nNewLength)
{
ATLASSERT( nNewLength >= 0 );
SetLength( nNewLength );
}
void SetLength(_In_ int nLength)
{
ATLASSERT( nLength >= 0 );
ATLASSERT( nLength <= GetData()->nAllocLength );
if( nLength < 0 || nLength > GetData()->nAllocLength)
AtlThrow(E_INVALIDARG);
GetData()->nDataLength = nLength;
m_pszData[nLength] = 0;
}
这个作用也比较明显,就是设置nDataLength ,并且把字符串结尾置0。
其他构造函数
一些关键函数
void ReleaseBuffer(_In_ int nNewLength = -1)
{
if( nNewLength == -1 )
{
int nAlloc = GetData()->nAllocLength;
nNewLength = StringLengthN( m_pszData, nAlloc);
}
SetLength( nNewLength );
}
如果ReleaseBuffer不传参数,那么nNewLength 就是m_pszData字符数与nAlloc之间的小者。否则nNewLength 由你自己设置。StringLengthN函数的功能就是取。SetLength( nNewLength )把nNewLength 位置的字符置为0,并把CStringData的nDataLength字段设置为nNewLength 。所以说ReleaseBuffer并不是用来释放内存的,函数名甚是迷惑人。