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;
&