我们都知道STL里有很多容器,如vector,stack,queue等等,每个容器都是通过配置器来获取存储空间的,现在来总结下配置器原理
当我们创建一个对象时常用方式是new,销毁一个对象用delete
T* a = new T;
Delete a;
对于new 其实是先调用为其配置内存,再调用相应的构造函数,对于delete是先调用析构函数再释放内存
内存配置操作是有allocate来完成,构造由construct完成
主要来看空间的配置,配置器定义位于<memory>中,其中负责内存空间的配置和析构的主要在下面头文件中
#include <stl_alloc.h>
- SGI两层配置器
SGI设置了两级配置器,对于大于128字节的申请用第一级配置器,小于则用第二级配置器,他们拿到内存空间的途径不同,主要思想是大块内存就直接malloc从内存里申请,小块的就先从内存池里申请,这样可以避免产生很多的外碎片。
- 一级配置器
直接调用malloc/free来申请空间,并有异常状态处理机制,即当内存配置需求无法被满足时会调用用户自己定义的一个指定函数,来看源码
template<int Inst>
class __MallocAllocTemplate //一级空间配置器
{
typedef void (*OOM_HANDLER)();
private:
static void* OOM_Malloc(size_t n);
static void* OOM_Realloc(void *p, size_t newSZ);
static OOM_HANDLER OOM_Handler;
public:
static void* Allocate(size_t n) //先调用allocate来申请内存空间
{
void* ret = malloc(n);
if (ret == NULL) //当没有这么大小的内存块是调用OOM_Malloc
ret = OOM_Malloc(n);
return ret;
}
static void Deallocate(void* p, size_t n)
{
free(p);
}
static void* Reallocate(void* p, size_t oldSZ, size_t newSZ)
{
void* ret = realloc(p, newSZ);
if (ret == NULL)
ret = OOM_Realloc(p, newSZ);
return ret;
}
static OOM_HANDLER SetMallocHandler(OOM_HANDLER f)
{
OOM_HANDLER old = OOM_Handler;
OOM_Handler = f; //获取用户自己设定的处理函数
return old;
}
};
template<int Inst>
void (*__MallocAllocTemplate<Inst>::OOM_Handler)() = NULL;
template<int Inst>
void* __MallocAllocTemplate<Inst>::OOM_Malloc(size_t n)
{
void* ret = NULL;
void(*myHandler)() = NULL;
for (;;)
{
myHandler = OOM_Handler;
if (myHandler == NULL) //如果用户没有设定出错处理函数,就抛出 bad_alloc异常信息
throw bad_alloc();
(*myHandler)();
ret = malloc(n);
if (ret != NULL)
return ret;
}
}
template<int Inst>
void* __MallocAllocTemplate<Inst>::OOM_Realloc(void* p, size_t newSZ)
{
void* ret = NULL;
void(*myHandler)() = NULL;
for (;;)
{
myHandler = OOM_Handler;
if (myHandler == NULL)
throw bad_alloc();
(*myHandler)();
ret = realloc(p, newSZ);
if (ret != NULL)
return ret;
}
}
typedef __MallocAllocTemplate<0> MallocAlloc;
可以看到allocate和realloc都是在调用malloc和realloc不成功后,改调用oom_malloc()和oom_realloc()
- 二级配置器
二级配置器管理了一个内存池并维护了一个自由链表,自由链表通过有16个节点。首先配置器会主动将任何小额区块的内存需求量上调至8的倍数,然后检查对应的free list,如果free-list中有可用的区块,就直接拿来,如果没有,就准备为对应的free-list 重新填充空间
static void* Allocate(size_t n)
{
//...
if (ret == NULL)
{
void* r = Refill(RoundUP(n));
}
//...
}
新的空间将取自内存池,缺省取20个新节点,如果内存池不足(还足以一个以上的节点),就返回的相应的节点数
static void* Refill(size_t n)
{
size_t nobjs = 20;
char* chunk = (char*)ChunkAlloc(n, nobjs); //默认获得20的新节点
if (nobjs == 1) //如果只有一块直接返回调用者
return chunk;
//有多块,返回一块给调用者,其他挂在自由链表中
Obj* ret = (Obj*)chunk;
Obj* cur = (Obj*)(chunk + n);
Obj* next = cur;
Obj* volatile *myFreeList = FreeList + FreeListIndex(n);
*myFreeList = cur;
for (size_t i = 1; i < nobjs; ++i)
{
next = (Obj*)((char*)cur + n);
cur->freeListLink = next;
cur = next;
}
cur->freeListLink = NULL;
return ret;
}
如果当内存池中连一个节点大小都不够时,就申请新的内存池,如果申请内存池成功就把原来内存池中剩下的空间分配给适当的free-list,如果找不到就调用第一级配置器,总的概括策略如下
- 将n字节扩展到8的倍数,找自由链表,有则分配,没有则向内存池申请20×n的内存块
- 若内存池小于20x n,但是比一块大小n要大,那么此时将内存最大可分配的块数给自由链表
- 如果整个内存池空间都不够了,就先将内存池残余的零头给挂在自由链表上,然后向系统heap申请空间,申请成功则返回,申请失败则到自己的自由链表中看看还有没有可用区块返回,如果连自由链表都没了最后会调用一级配置器
小结:可以看到STL内存分配器是基于空闲链表的最佳分配策略,对小内存申请进行了优化,可以避免内存碎片产生同时最大化内存利用率