VS2010 CString源码分析

从内存构造开始说起

我们定义一个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*类型。

看看的CStringDatadata函数,返回的是一个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共享一块内存,现在要自立内存了,也要把之前的数据拷过来吧。

CStringDataRelease()也比较简单,就是引用计数<=0就把内存释放掉。

void Release() throw()
{
	ATLASSERT( nRefs != 0 );

	if( _AtlInterlockedDecrement( &nRefs ) <= 0 )
	{
		pStringMgr->Free( this );
	}
}
void CAfxStringMgr::Free( CStringData* pData ) throw()
{
	free(pData);
}

现在回到CStringToperator= 函数:

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,并把CStringDatanDataLength字段设置为nNewLength 。所以说ReleaseBuffer并不是用来释放内存的,函数名甚是迷惑人。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值