分享
 
 
 

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

王朝vc·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

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方法上.

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有