MFC的CString(VC6) 内存管理分析

MFC的CString(VC6) 内存管理分析
发布时间:03-19

<script xml:space="preserve" type="text/javascript"> // // <!--google_ad_client = "pub-7660113768111433";google_ad_width = 728;google_ad_height = 90;google_ad_format = "728x90_as";google_ad_type = "text_image";google_ad_channel = "";google_color_border = "CCCCCC";google_color_bg = "CCCCCC";google_color_link = "000000";google_color_text = "000000";google_color_url = "000000";//--> // // </script> <script src="http://pagead2.googlesyndication.com/pagead/show_ads.js" xml:space="preserve" type="text/javascript"> </script>
CString 类是我们经常用到的类,所以有必要对它的内存管理模式分析一下.
CString 内存管理的演变过程如下:
   VC5  单纯的使用new delete方法。
因为字符串操作需要频繁调整内存大小.而采用C++操作符 new 与 delete
是没有与realloc相应功能的。结果就是每一次的改变内存大小都需要额外
增加一次拷贝操作。
而 new 与delete 在实现中在进程堆中分配。频繁地在堆上进行小内存分配与释放
必然在堆上产生大量碎片。堆碎片过多直接影响了程序效率。
于是MFC在VC6版本对此进行了改进。

VC6 对于大于512字节的内存和DEBUG模式下,CString仍然使用 new 和 delete来操纵。
在Release模式下不大于512字节的内存分配操作采用了内存池管理。
并将之细分为 <=64, <=128, <=256, <=512 字节4个内存池管理。
这样在不大于512字节的情况下CString有了很好的效率。
但是传说中有解决一个BUG就会产生另外一个BUG的定律。
CString 显然也无法避免它。

于是在VC7中又改了。

VC7 恢复使用C 的内存管理调用方式。即采用 alloc, free, realloc. CString存在的问题
就是由于new与delete没有realloc重新调整内存大小的功能。之前产生的问题导致最终
还是采用了C的管理方法。
在VC6中为了解决CString小内存操纵的性能问题 MFC在Release版本下对于不大于512字节的内存分配
采用的内存池管理来进行优化。其他情况下仍旧使用new 与delete.
Release版本下CString在处理不大于512Byte字串的内存时调用如下
VC6 中CString 分配内存与释放内存调用次序如下
CString::AllocBuffer 
CFixedAlloc::Alloc
CPlex::Create
CString::FreeData
CFixedAlloc::Free
=========================================================================================
相关代码引用如下:
FILE:MFC / SRC / STRCORE.CPP
void  CString::AllocBuffer( int  nLen)  // 用来分配内存
{
    ... 
 #ifndef _DEBUG   
// 在Release 版本并且是不大于512字节
 if (nLen <= 64)
        
{
     pData 
= (CStringData*)_afxAlloc64.Alloc();
            pData
->nAllocLength = 64;
 }

 
else 分别为<= 1128<=256 , <=512
 
{
            ...
 }

 
else
#endif           // DEBUG 和Release下大于512的
 
{
     pData 
= (CStringData*)
     
new BYTE[sizeof(CStringData) + (nLen+1)*sizeof(TCHAR)];
     pData
->nAllocLength = nLen;
 }

   ...
}


void  FASTCALL CString::FreeData(CStringData *  pData)  //  释放内存
{
#ifndef _DEBUG  在Release 版本并且是不大于512字节
 
int nLen = pData->nAllocLength;
 
if (nLen == 64)  // 根据内存大小分别调用管理器
    _afxAlloc64.Free(pData);
 
else if (nLen == 128)
    _afxAlloc128.Free(pData);
 
else if (nLen == 256)
    _afxAlloc256.Free(pData);
 
else  if (nLen == 512)
    _afxAlloc512.Free(pData);
 
else
 
{
  ASSERT(nLen 
> 512);
  delete[] (BYTE
*)pData;
 }

#else  // DEBUG 和Release下大于512的
 delete[] (BYTE
*)pData;
#endif
}



_afxAlloc[64,128,256,512] 是CFixedAlloc类的全局对象。
我们分析一下CFixedAlloc是整样进行内存池管理的它在使用中又产生了什么问题?

class  CFixedAlloc   // 定义在 MFC/SRC/FIXALLOC.H文件中
{
public:
 CFixedAlloc(UINT nAllocSize, UINT nBlockSize 
= 64);
 UINT GetAllocSize() 
return m_nAllocSize; }
public:
 
void* Alloc();  //分配 由CString调用
 void Free(void* p); //释放 由CString调用
 void FreeAll(); //释放所有 被析构函数调用
public:
 
~CFixedAlloc();
protected:
 
struct CNode{//这个是用来实现一个单向链表
     CNode* pNext;   
 }
;
 UINT m_nAllocSize;  
// 需要分配对象的大小仅由构造函数传入
 UINT m_nBlockSize;  // 预分配的数目即池的大小,由构造函数赋予,可知默认为64
 CPlex* m_pBlocks;   // 池的链表指针。CPlex对象含有一个CPlex* pNext指针对象,
 CNode* m_pNodeFree; // 被释放块链表的头指针,实际是应看做可用内存块链表
 CRITICAL_SECTION m_protect;//临界区对象
}
;

/*
 在Alloc的实现中我们可以看到,当池中没有可用块的时候
 调用 CPlex::Create建立一块 m_nAllocSize * m_nBlockSize的内存池
 如果有的话则从m_pNodeFree中弹出一块来使用
*/

void *  CFixedAlloc::Alloc()
{
    
if (m_pNodeFree == NULL)//如果没有可用的内存块就进行分配一个池
       CPlex* pNewBlock = NULL;
       TRY
// 分配内存块 默认是64个m_nAllocSize.
     pNewBlock = CPlex::Create(m_pBlocks, m_nBlockSize, m_nAllocSize);
       }
CATCH_ALL(e){
            ...异常
       }
END_CATCH_ALL
       
// 下面的代码是将内存块压入m_pNodeFree链表中待用。
       CNode* pNode = (CNode*)pNewBlock->data();
       (BYTE
*&)pNode += (m_nAllocSize * m_nBlockSize) - m_nAllocSize;
       
for (int i = m_nBlockSize-1; i >= 0; i--, (BYTE*&)pNode -= m_nAllocSize)
       
{
    pNode
->pNext = m_pNodeFree;
    m_pNodeFree 
= pNode;
       }

    }

    
// 这两句是弹出一块内存给调用者使用。
    void* pNode = m_pNodeFree;
    m_pNodeFree 
= m_pNodeFree->pNext;
    ...
    
return pNode;
}

/*
  当调用者调用Free时,只是将这块内存重新压入m_pNodeFree链表中
  并非释放,而是标志为可用块以待后用。
*/

void  CFixedAlloc::Free( void *  p)
{
    
if (p != NULL)
    
{
        EnterCriticalSection(
&m_protect);
        CNode
* pNode = (CNode*)p;
        pNode
->pNext = m_pNodeFree;
        m_pNodeFree 
= pNode;
        LeaveCriticalSection(
&m_protect);
    }

}

void  CFixedAlloc::FreeAll()
{
    EnterCriticalSection(
&m_protect);
    m_pBlocks
->FreeDataChain();
    m_pBlocks 
= NULL;
    m_pNodeFree 
= NULL;
    LeaveCriticalSection(
&m_protect);
}

/*
   在析构函数中 调用FreeAll进行释放内存
*/

CFixedAlloc::
~ CFixedAlloc()
{
    FreeAll();
   DeleteCriticalSection(
&m_protect);
}



/*
   MFC/INCLUDE/AFXPLEX_.H
*/

struct  CPlex      //  warning variable length structure
{
    CPlex
* pNext;
    
void* data() return this+1; }
    
static CPlex* PASCAL Create(CPlex*& head, UINT nMax, UINT cbElement);
    
void FreeDataChain();       // free this one and links
}
;

/*
   MFC/SRC/PLEX.CPP
*/

CPlex
*  PASCAL CPlex::Create(CPlex *&  pHead, UINT nMax, UINT cbElement)
{
    CPlex
* p = (CPlex*new BYTE[sizeof(CPlex) + nMax * cbElement];
    p
->pNext = pHead;
    pHead 
= p;  // 加入链表
    return p;
}

void  CPlex::FreeDataChain()      //  free this one and links
{
   CPlex
* p = this;
   
while (p != NULL){
      BYTE
* bytes = (BYTE*) p;
      CPlex
* pNext = p->pNext;
      delete[] bytes;
      p 
= pNext;
   }

}



============================================================================
现在我们用一个实例来看一下在Release版本下的实际内存动作
以分配10000个 含有"abcdefghijklmnopqrstuvwxyz"串的CString数组
CString  * strArray[10000];
for( int =0;i < 10000; i++ )
strArray[i] = new CString("abcdefghijklmnopqrstuvwxyz");
因为字符串小于64所以调用了_afxAlloc64::Alloc;
----------------------------------------------------------------
_afxAlloc64在STRCORE.CPP中被定义如下:
AFX_STATIC CFixedAlloc _afxAlloc64(ROUND4(65*sizeof(TCHAR)+sizeof(CStringData)));
在ANSI版本下 sizeof(TCHAR) = 1
sizeof( CStrginData ) = 12;
65*sizeof(TCHAR)+sizeof(CStringData) = 77;
ROUND4定义用下,将之圆整为4的倍数,
#define ROUND(x,y) (((x)+(y-1))&~(y-1))
#define ROUND4(x) ROUND(x, 4)
所以
_afxAlloc64(ROUND4(65*sizeof(TCHAR)+sizeof(CStringData))) 实际上
宏展开最终为
extern CFixedAlloc _afxAlloc64( 80,64);
----------------------------------------------------------------
在CPlex中分配池的大小
sizeof(CPlex) + nMax * cbElement = 4+80*64 = 5124 BYTE.
因为10000不是64的整数倍 = 要分配157个池
实际分配内存 = 157*5124 = 804468 BYTE = 804KB.
释放CString对象
for( int =0;i < 10000; i++ )
delete strArray[i];
此时CString 的调用_afxAlloc64.Free.
由CFixedAlloc::Free的实现可知此时并没有真正释放内存,只是将这该块重新加入m_pNodeFree链表中待用.
因为CFixedAlloc释放内存操作是在析构函数调用,而_afxAlloc64是被定义为全局对象.它的析构函数要到程序退出才能被调用.
所以CFixedAlloc分配的内存在程序结束之前只会增加而不能回收.
而如果我们重新分配10000个 字符串>64 <=128的的CString对象时
_afxAlloc64的内存占用依旧,而_afxAlloc128则重新分配了 157*(4+144*64) = 157*9220=1447540= 1.44754MB
再释放它,此时内存占用则为 1.44754MB+804KB = 2.252008MB.
与使用char*对象做比较:
char*   chArray[10000];
分配 "abcdefghijklmnopqrstuvwxyz" 实际内存是 27*10000 = 270KB
释放后内存即被回收
再分配128字串 10000个 内存是 129*10000 = 1.29MB.
释放后内存即被回收
结论:
VC6中的CString采用内存池技术在改进小内存new与delete的性能与堆碎片问题后
又产生了一个不是内存泄露的内存泄露。
其实VC5,VC6中CString产生的问题是因为教条地尊守C++应当采用new与delete来管理内存的规则造成的
最终在VC7中 CString仍旧回到使用C方法上.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值