CPP内存管理 第二章 分配器
VC6.0 malloc
VC6的malloc函数分配的内存除了申请的空间之外,还有cookie以及一些额外的overhead。如果大量调用malloc会造成很大浪费。
VC6.0的allocate()函数只是对malloc的二次封装,并没有做什么很特殊的操作,它是以类型字节长度为单位分配内存的,上图就分配了512个int类型空间。
BC5的allocate( )函数和VC6.0本质一样。
G2.9 allocator
G2.9 std::alloc
gcc2.9版本如图所示,但是实际中却没有包含使用上图的allocator(),而是使用下图的std::alloc
上图的容器使用Alloc可见,实际的分配器使用的是一个alloc的类,该类分配内存是以字节为单位的,而不是以对象为单位。可以使用alloc::allocate(512),这路分配的是512字节,而不是512个对象。
G2.9std::alloc VS G4.9__pool_alloc
gcc4.9版本,2.9版本的allocate不属于正式使用的版本了,而使用__pool_alloc,2.9版本的allocate和4.9版本的__pool_alloc效果相同。
测试发现:__gun_cxx::__pool_alloc分配3个8B的double,相距08H,证明不带cookie
而std::allocator的带cookie。
std::alloc解读
std::alloc使用一个带有16个元素为指针的数组,数组不同的元素管理不同的区块,#0表示8个字节的大小,#1表示16个字节的大小,以此类推。当分配的时候不足会按照8字节向上取整,如需要一个6字节会分配8个字节的大小。若超过128B的话std::alloc会直接调用malloc给用户分配空间。
申请32B
pool:预留的空间,方便用户后续需要时不需要重新申请空间。
RoundUp:实现8字节对齐.例如传入13,传出的就是16. 0>>4表示右移4位
嵌入式指针:将每一块的前四个字节用作指针连接下一块可用的内存块,可以节省空间。(空闲的空间未分配,所以使用前4个字节无可厚非;已分配的内存会将地址返回给用户,所以不需要使用这个指针。
假如现在需要申请32B的大小,先判断#3对应的指针域为空,且pool为空, std::alloc会申请32202+RoundUp并将32*20的空间分成20块然后分配第一块,并将后续19块挂到#3上作为后续分配。后续640B会作为pool来为后续使用。
申请64B
pool:只要能提供1~20个就可以使用pool
申请64B的大小,发现#7为空,但是此时pool不为空,且这个pool可以提供10个(在1~20之间)此时将原来的pool作为这个64B的挂到#7上,并分配一块给用户,分配之后pool置为0。这样做的好处是可以减少使用malloc的次数,因为若没有pool,此时需要重新使用malloc,这个malloc的空间带cookie,浪费空间。
申请96B
由于pool没有剩余的空间,所以会重新malloc,96202+RoundUp,其余同上。
申请88B
申请88B的大小,发现#10为空,但是此时pool不为空,且这个pool可以提供,但是这时候pool可以提供不止20个,编译器也只会提供最多20个,并从中分配一块给用户,此时pool剩余2000-88*20=240。
继续申请3块88B
申请88B的大小,发现#10不为空,直接从链表上摘下一块分配给用户。
申请8B
申请8B的大小,发现#0为空,但是此时pool不为空,且这个pool可以提供,但是这时候pool可以提供不止20个,编译器也只会提供最多20个,并从中分配一块给用户,此时pool剩余240-8*20=80。
申请104B
此时pool提供的余量不够供应,先将pool的剩余空间(内部碎片)挂到#9上。再利用malloc申请104202+RoundUp,分配一块给用户。
申请112B
申请112B的大小,发现#13为空,但是此时pool不为空,且这个pool可以提供,这时候pool可以提供不止20个,但是编译器也只会提供最多20个,并从中分配一块给用户,此时pool剩余2408-112*20=168。
申请48B
申请48B的大小,发现#5为空,但是此时pool不为空,且这个pool可以提供3个(1~20之间),并从中分配一块给用户,此时pool剩余168-48*3=24。
系统可分配的内存不足,如何处理
申请72B
申请72B的大小,发现#8为空,但是此时pool不为空,但是这个pool又不足以供应一个,此时进行碎片处理,将pool剩余的24B挂到#2上。然后需要malloc对应的大小,但是此时若系统的heap大小为10000,而此时已分配的为9688,此时再向重新malloc会失败,此时会和后边的链表借,因为#8后面的#9此时满足,从中切除72B分配给用户,此时pool剩余80-72=8B。剩余的72B则返回给用户。
再次申请72B的大小,发现pool不足以分配,将pool的内存碎片处理pool=0,并查看#9发现已经没用可用的,继续查看#10,发现有可用,从中切除72B分配给用户,此时pool剩余88-72=16。
申请120B
申请120B,此时已经无法找到合适的借用
检讨
检讨一
alloc已经分配出去的空间,但是此时没有被有效使用,但是回收起来很麻烦。因为在未分配的空间是通过指针串起来的,系统可能经过多次的申请和释放(这里释放并不是指给OS回收,而是返回给这个std::allloc,这里OS并没有回收分配的内存)这个时候空间可能早就不是连续的了,如下图,回收难度很大。
检讨二
像上面的例子剩余312B,可以将失败的那次通过一直折半折半再折半,最终获取<=312可以满足,实现起来不是很难,但是在GCC中并没有采取任何做法。