MFC的CString的内部实现分析

本文详细探讨了MFC CString类的构造过程、内存管理机制以及关键函数实现,包括引用计数、CopyBeforeWrite技术和内存分配策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MFC的CString是字符串管理类,其为了实现高效率的缓冲管理,使用了引用记数及CopyBeforeWrite技术。这在一定程度上加大了其神秘感和理解难度。好在他的代码是公开的,所以可以给我们研究它的内部实现提供条件。下面就来看看到底是如何实现的。由于不同版本的MSVC其CString实现有些许差别,下面是针对VS2003来说明的。

由于它的基础类使用了模板技术,可以实现内存管理的定制,我们只研究MFC的默认内存管理方式。即CAfxStringMgr类提供的方式。MFC在strcore.cpp定义了它的一个全局实例:CAfxStringMgr afxStringManager。在特性模板参数中通过

AfxGetStringManager() 将 afxStringManager作为默认的管理对象指针。

下面从具体的代码来查看其执行机制:

CString str( _T("abc") );

这个语句定义了字串对象str,并初始化为"abc"。

看它到底是如何执行的吧。

首先CString是 CStringT 的TCHAR字符类型版本(自适应Unicode及Ascii型)。

typedef ATL::CStringT< TCHAR, StrTraitMFC< TCHAR > > CString;

所以它的构造函数是调用了

 

  1. CStringT::CStringT( const XCHAR* pszSrc ) :  
  2.   CThisSimpleString( StringTraits::GetDefaultManager() )  
  3.  {  
  4.   if( !CheckImplicitLoad( pszSrc ) )  
  5.   {  
  6.    // nDestLength is in XCHARs  
  7.    *this = pszSrc;  
  8.   }  
  9.  }  

 

可见在构造函数使用CThisSimpleString( StringTraits::GetDefaultManager() ) 指定了内存管理对象为afxStringManager。记住这个MFC默认的内存管理返回的字串内存为空,但带了一个内存分配器,为以后的内存分配做准备。因为字串内存为空,所以后面的赋值操作将激发内存分配操作。

然后调用赋值语句

 

  1. CStringT& operator=( PCXSTR pszSrc )  
  2.     {  
  3.         CThisSimpleString::operator=( pszSrc );  
  4.   
  5.         return( *this );  
  6.     }  

 

这里又将处理转向了基类CSimpleString的赋值语句

 

  1. CSimpleStringT& operator=( PCXSTR pszSrc )  
  2.     {  
  3.         SetString( pszSrc );  
  4.   
  5.         return( *this );  
  6.     }  

 

下面就进入了字串赋值的实际代码

 

  1. void SetString( PCXSTR pszSrc )  
  2.     {  
  3.         SetString( pszSrc, StringLength( pszSrc ) );  
  4.     }  
  5.     void SetString( PCXSTR pszSrc, int nLength )  
  6.     {  
  7.         if( nLength == 0 )  
  8.         {  
  9.             Empty();  
  10.         }  
  11.         else  
  12.         {  
  13.             // It is possible that pszSrc points to a location inside of our   
  14.             // buffer.  GetBuffer() might change m_pszData if (1) the buffer   
  15.             // is shared or (2) the buffer is too small to hold the new   
  16.             // string.  We detect this aliasing, and modify pszSrc to point  
  17.             // into the newly allocated buffer instead.  
  18.               
  19.             if(pszSrc == NULL)  
  20.                 AtlThrow(E_INVALIDARG);           
  21.                   
  22.             UINT nOldLength = GetLength();  
  23.             UINT_PTR nOffset = pszSrc-GetString();  
  24.             // If 0 <= nOffset <= nOldLength, then pszSrc points into our   
  25.             // buffer  
  26.   
  27.             PXSTR pszBuffer = GetBuffer( nLength );  
  28.             if( nOffset <= nOldLength )  
  29.             {  
  30.                 CopyCharsOverlapped( pszBuffer, pszBuffer+nOffset, nLength );  
  31.             }  
  32.             else  
  33.             {  
  34.                 CopyChars( pszBuffer, pszSrc, nLength );  
  35.             }  
  36.             ReleaseBufferSetLength( nLength );  
  37.         }  
  38.     }  

 

可以看出它考虑了新旧字串地址的重叠问题,并做了不同处理。

其中最关键的函数是GetBuffer(nLength);

 

  1. PXSTR GetBuffer( int nMinBufferLength )  
  2.  {  
  3.   return( PrepareWrite( nMinBufferLength ) );  
  4.  }  
  5.   
  6.   
  7.   
  8. PXSTR PrepareWrite( int nLength )  
  9.  {  
  10.   CStringData* pOldData = GetData();  
  11.   int nShared = 1-pOldData->nRefs;  // nShared < 0 means true, >= 0 means false  
  12.   int nTooShort = pOldData->nAllocLength-nLength;  // nTooShort < 0 means true, >= 0 means false  
  13.   if( (nShared|nTooShort) < 0 )  // If either sign bit is set (i.e. either is less than zero), we need to copy data  
  14.   {  
  15.    PrepareWrite2( nLength );  
  16.   }  
  17.   
  18.   return( m_pszData );  
  19.  }  
  20.  ATL_NOINLINE void PrepareWrite2( int nLength )  
  21.  {  
  22.   CStringData* pOldData = GetData();  
  23.   if( pOldData->nDataLength > nLength )  
  24.   {  
  25.    nLength = pOldData->nDataLength;  
  26.   }  
  27.   if( pOldData->IsShared() )  
  28.   {  
  29.    Fork( nLength );  
  30.   }  
  31.   else if( pOldData->nAllocLength < nLength )  
  32.   {  
  33.    // Grow exponentially, until we hit 1K.  
  34.    int nNewLength = pOldData->nAllocLength;  
  35.    if( nNewLength > 1024 )  
  36.    {  
  37.     nNewLength += 1024;  
  38.    }  
  39.    else  
  40.    {  
  41.     nNewLength *= 2;  
  42.    }  
  43.    if( nNewLength < nLength )  
  44.    {  
  45.     nNewLength = nLength;  
  46.    }  
  47.    Reallocate( nNewLength );  
  48.   }  
  49.  }  

 

其中

if( (nShared|nTooShort) < 0 )  // If either sign bit is set (i.e. either is less than zero), we need to copy data
  {
   PrepareWrite2( nLength );
  }

说明假设字串缓冲是共享的(也就是多个CString对象共用),将激发Fork( nLength );,假如内存太小,将进行内存扩大操作。

在本例中,也许大家觉得这个初始时内存为0,肯定进行内存扩大操作。但实际上进入的是Fork(nLength);,因为原来初始化的默认内存管理器所返回的内存天生属于共享的。

 

  1. class CNilStringData :  
  2.     public CStringData  
  3. {  
  4. public:  
  5.     CNilStringData() throw()  
  6.     {  
  7.         pStringMgr = NULL;  
  8.         nRefs = 2;  // Never gets freed by IAtlStringMgr  
  9.         nDataLength = 0;  
  10.         nAllocLength = 0;  
  11.         achNil[0] = 0;  
  12.         achNil[1] = 0;  
  13.     }  
  14.   
  15.     void SetManager( IAtlStringMgr* pMgr ) throw()  
  16.     {  
  17.         ATLASSERT( pStringMgr == NULL );  
  18.         pStringMgr = pMgr;  
  19.     }  
  20.   
  21. public:  
  22.     wchar_t achNil[2];  
  23. };  

 

nRefs = 2;  // Never gets freed by IAtlStringMgr

这里将默认返回的内存的nRefs设置为2,就是初始的字串都共享原始的那个长度为0的缓冲。

下面看看Fork函数如何处理:

 

  1. ATL_NOINLINE void Fork( int nLength )  
  2.     {  
  3.         CStringData* pOldData = GetData();  
  4.         int nOldLength = pOldData->nDataLength;  
  5.         CStringData* pNewData = pOldData->pStringMgr->Clone()->Allocate( nLength, sizeof( XCHAR ) );  
  6.         if( pNewData == NULL )  
  7.         {  
  8.             ThrowMemoryException();  
  9.         }  
  10.         int nCharsToCopy = ((nOldLength < nLength) ? nOldLength : nLength)+1;  // Copy '/0'  
  11.         CopyChars( PXSTR( pNewData->data() ), PCXSTR( pOldData->data() ), nCharsToCopy );  
  12.         pNewData->nDataLength = nOldLength;  
  13.         pOldData->Release();  
  14.         Attach( pNewData );  
  15.     }  

 

我们看到这里对缓冲内存进行了重新分配。同时对旧的缓冲调用Release。

其中用到了默认的内存管理器进行内存分配操作

pOldData->pStringMgr->Clone()->Allocate( nLength, sizeof( XCHAR ) );

这个分配操作就是内部引用计数的关键部分。

 

  1. CStringData* CAfxStringMgr::Allocate( int nChars, int nCharSize )  
  2. {  
  3.     size_t nTotalSize;  
  4.     CStringData* pData;  
  5.     size_t nDataBytes;  
  6.   
  7.     ASSERT(nCharSize > 0);  
  8.       
  9.     if(nChars < 0)  
  10.     {  
  11.         ASSERT(FALSE);  
  12.         return NULL;  
  13.     }  
  14.       
  15.     nDataBytes = (nChars+1)*nCharSize;  
  16.     nTotalSize = sizeof( CStringData )+nDataBytes;  
  17.     pData = (CStringData*)malloc( nTotalSize );  
  18.     if (pData == NULL)  
  19.         return NULL;  
  20.     pData->pStringMgr = this;  
  21.     pData->nRefs = 1;  
  22.     pData->nAllocLength = nChars;  
  23.     pData->nDataLength = 0;  
  24.   
  25.     return pData;  
  26. }  

 

我们看到这里的分配操作额外分配了CStringData大小的数据在缓冲的前面,该数据用于对缓冲区进行引用计数。从而实现了缓冲的共享。

然后其他操作时只要涉及数据Clone或释放操作时都将对计数进行增加或减少,从而达到缓冲共享,并在不使用时能够及时回收。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值