为什么需要空间配置器呢?
原因一:当系统频繁的申请释放内存时,会造成内存碎片问题,导致明明剩余的总内存够需要分配的内存,但是由于这样内存不是连续的,而是一块块的,每一小块又达不到需要分配的内存的大小,所以造成内存碎片问题(外碎片),但是,当我们提前将这些内存按照不同大小组织起来,需要分配时,根据需要分配不同大小的内存,这样便能很好地缓解内存碎片问题(外碎片),但是也引入了内碎片问题。
原因二:
频繁分配小块内存效率较低,我们可以预先分配一块大的内存,并将这块内存用一定的结构组织起来,当需要分配小块内存时,从组织结构中分配,用完又还给组织结构。
由于以上两点,STL中引入了空间配置器的概念,空间配置器就是原因二中的组织结构。
框架分析:
代码详细分析:
#include<iostream>
#include <new>
using namespace std;
//一级空间配置器
template<int inst>
class __MallocAllocTemplate{
private:
//Allocate中malloc调用失败时的逻辑处理函数,会调用__MallocAllocOomHandler句柄函数(说明见下)
static void* OomMalloc(size_t);
//Reallocate中realloc调用失败时的逻辑处理函数,会调用__MallocAllocOomHandler句柄函数(说明见下)
static void* OomRealloc(void*, size_t);
//内存释放函数句柄,当malloc、reallooc调用失败时,
//即向系统申请内存失败时,此句柄不为空则调用句柄处理函数释放内存。类似C++的set_new_handler
static void(*__MallocAllocOomHandler)();
public:
//一级空间配置器对外接口
static void* Allocate(size_t n){ //直接申请内存,malloc的封装
void* result = malloc(n);
//当malloc申请内存失败时,即向系统申请内存失败时,调用OomMalloc函数(说明见下)进一步进行处理
if (0 == result){
result = OomMalloc(n);
}
return result;
}
static void Deallocate(void* p,size_t){ //释放内存,free的封装
free(p);
}
static void* Reallocate(void* p, size_t new_sz){ //先释放旧内存再申请新内存,realloc的封装
void* result = realloc(p, new_sz);
//当realloc申请调用失败时,即向系统申请内存失败时,调用OomRealloc函数(说明见下)进一步进行处理
if (0 == result){
result = OomRealloc(p, new_se);
}
return result;
}
//SetMallocHandler函数用于设置__MallocAllocOomHandler句柄
//参数为新的句柄,返回值为旧的句柄。
static void(*SetMallocHandler(void(*f)()))(){
void(*old)() = __MallocAllocOomHandler;
__MallocAllocOomHandler = f;
return old;
}
};
//模板的静态变量的初始化动作,此处为不设置内存释放函数
template<int inst>
void(*__MallocAllocTemplate<inst>::__MallocAllocOomHandler)() = 0;
//函数说明见上
template<int inst>
void* __MallocAllocTemplate<inst>::OomMalloc(size_t n){
void(*myMallocHander)();
void* result;
//在Allocate中malloc调用失败时,会调用此函数,此函数的for循环(死循环)中
//会判断是否设置__MallocAllocOomHandler内存释放函数句柄
//若没有设置直接抛出bad_alloc异常,若设置有内存释放函数句柄,则调用__MallocAllocOomHandler释放内存
//再次malloc申请内存,若申请到则返回内存句柄,否则循环上述过程直至申请内存成功。
for (;;){
myMallocHander = __MallocAllocOomHandler;
if (0 == myMallocHander){
throw bad_alloc;
}
(*myMallocHander)();
result = malloc(n);
if (result){
return result;
}
}
}
//函数说明见声明处,函数功能类似于OomMalloc函数
template<int inst>
void* __MallocAllocTemplate<inst>::OomRealloc(void* p, size_t n){
void(*myMallocHeadler)();
void* result;
for (;;){
myMallocHeadler = __MallocAllocOomHandler;
if (0 == myMallocHeadler){
throw bad_alloc;
}
(*myMallocHeadler)();
result = realloc(p, n);
if (result){
return result;
}
}
}
//二级空间配置器的实现原理是:
//申请时:
//首次通过一级空间配置器申请到一块内存,
//并通过start_free、end_free指针管理起来,当程序再次需要申请内存时,
//如果二级空间配置器可以满足要求,则通过二级空间配置器直接分配,
//否则直接调用一级空间配置器进行分配,
//释放时:
//若释放的内存可被二级空间配置器管理起来,则直接被二级空间配置器管理起来以备下次使用
//否则,直接调用一级空间配置器的Deallocate进行释放。
//
//二级空间配置器的内存管理是通过free_list(自由链表)和start_free、end_free两个内存指针一块进行管理的
//自由链表的结构类似于哈希桶,每个数组元素下面挂接的链表中的元素是内存相同的小块内存,
//不同的数组元素下面挂接的链表中的元素内存大小又各不相同
//从下标0开始的数组元素下面挂接的自由链表元素内存大小依次具有一定间距,
//如:free_list[0]下为8Byte,free_list[1]下为16Byte... 此时,间距为8Byte,当链表下挂的最大内存块为
//128Byte时,free_list的大小为16个元素,当然,间距和最大内存是可以变化的,所以free_list的大小也随前两着而变化
//二级空间配置器,threads为多线程预留参数。
template<bool threads,int inst>
class __DefaultAllocTemplate{
private:
enum{ __ALIGN = 8 }; //间距
enum{__MAX_BYTES = 128}; //最大内存
enum{ __NFREELISTS = __MAX_BYTES/__ALIGN }; //free_list(自由链表)大小
//将要申请内存对齐到8的倍数,1-8对齐到8、9-16对齐到16.....
static size_t ROUND_UP(size_t bytes){
return (bytes + _ALIGN - 1)& ~(__ALIGN - 1);
}
//用于将自由链表下面挂接的内存块链接起来
union obj{
union obj* free_list_link; //指向下一个内存块
char client_data[1]; //为了编译器更好的优化,迎合Pointer aliasing规则
};
//自由链表
static obj* volatile free_list[__NFREELISTS];
//定位想要申请的bytes字节内存块对应在自由链表中的下标位置
//1-8对应0号下标、9-16对应1号下标
static size_t FREELIST_INDEX(size_t bytes){
return (bytes + __ALIGN - 1) / __ALIGN - 1;
}
static void* Refill(size_t n);
static char* ChunkAlloc(size_t size, int& nobjs);
//管理从一级空间配置器申请的内存
static char* start_free; //指向内存开始处
static char* end_free; //指向内存结尾处 此范围左闭右开[ )
static size_t heap_size;
public:
static void* Allocate(size_t n){
obj* volatile *my_free_list;
obj* result;
//如果要申请的内存大于自由链表中可管理的最大内存,直接呼叫一级空间配置器进行申请
if (n > (size_t)__MAX_BYTES){
return MallocAlloc::Allocate(n);
}
//如果要申请的内存小于自由链表中可管理的最大内存,定位到大于要申请内存的最小内存链表下标处
my_free_list = free_list + FREELIST_INDEX(n);
result = *my_free_list;
//如果此链表下无内存,调用Refill函数进行处理(说明见下)
if (result == 0){
void* r = Refill(ROUND_UP(n));
return r;
}
//如果有内存直接摘取一个内存块返回
*my_free_list = result->free_list_link;
return result;
}
static void Deallocate(void* p,size_t n){
obj* q = (obj*)p;
obj* volatile * my_free_list;
//如果要释放的内存大于自由链表中可管理的最大内存,直接呼叫一级空间配置器进行释放
if (n >(size_t)__MAX_BYTES){
MallocAlloc::Deallocate(p, n);
return;
}
//如果自由链表可以管理该块内存,则直接挂接到自由链表下
my_free_list = free_list + FREELIST_INDEX(n);
q->free_list_link = *my_free_list;
*my_free_list = q;
}
static void* Reallocate(void* p, size_t old_sz, size_t new_sz);
};
//二级空间配置器核心的申请内存的逻辑函数,nobjs输入输出形参数,输入时表示想要申请的内存块数量,
//输出时表示实际申请到内存块的数量
template<bool threads,int inst>
char* __DefaultAllocTemplate<threads, inst>::ChunkAlloc(size_t size, int& nobjs){
char* result;
//想要申请的内存大下
size_t total_bytes = size*nobjs;
//start_free和end_free管理的内存大小
size_t bytes_left = end_free - start_free;
//如果能满足想要申请的内存的大小,则直接分配,并返回
if (bytes_left >= total_bytes){
result = start_free;
start_free += total_bytes;
return result;
}//如果不能满足想要申请的内存块数,但至少可以分配一块内存,则分配最大可能的内存块
else if(bytes_left >= size){
nobjs = bytes_left / size;
total_bytes = size*nobjs;
result = start_free;
start_free += total_bytes;
return result;
}//连一块内存都不能满足,则直接向系统malloc内存,内存大小为:2 * total_bytes+调整值(ROUND_UP(heap_size >> 4))
else{
size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
//如果bytes_left还有内存,将次内存挂接到对应自由链表下面
if (bytes_left > 0){
obj* volatile * my_free_list = free_list + FREELIST_INDEX(bytes_left);
((obj*)start_free)->free_list_link = *my_free_list;
*my_free_list = (obj*)start_free;
}
//向系统申请内存
start_free = (char*)malloc(bytes_to_get);
//申请失败,看自由链表中是否还有大于等于bytes_to_get的内存块,如果有,用满足要求的最小内存块进行分配
if (0 == start_free){
int i;
obj * volatile * my_free_list, *p;
//查找满足要求的最小内存块
for (i = size; i <= __MAX_BYTES; i += __ALIGN){
if (i >= bytes_to_get){
my_free_list = free_list + FREELIST_INDEX(i);
p = *my_free_list;
if (0 != p){
*my_free_list = p->free_list_link;
start_free = (char*)p;
end_free = start_free += i;
retuan ChunkAlloc(size, nobjs);
}
}
}
//注意必须将end_free置为空,否则当下面的MallocAlloc::Allocate申请内存失败时,
//抛出异常,程序捕捉异常,当下此再次调用ChunkAlloc时end_free - start_free会得到一个很大值(start_free为空),从而使程序出错
end_free = 0;
//当自由链表中没有满足要求的内存块时,调用一级空间配置器分配内存,因为一级空间配置器可能会设置有内存释放函数
start_free = (char*)MallocAlloc::Allocate(bytes_to_get);
}
//程序运行到此步说明申请内存成功,调整总共向系统申请的内存总数heap_size,以及调整end_free,最后调用自己ChunkAlloc函数进行分配内存
heap_size += bytes_to_get;
end_free = start_free + bytes_to_get;
return ChunkAlloc(size, nobjs);
}
}
//当自由链表下面没有挂接要申请内存块大小(对齐后)的内存时调用此函数
template<bool threads,int inst>
void* __DefaultAllocTemplate<threads, inst>::Refill(size_t n){
int nobjs = 20;
//调用ChunkAlloc试图申请更多的内存(20倍),ChunkAlloc的nobjs是输入输出型参数,表示正确申请到多少内存(说明见上)
char* chunk = ChunkAlloc(n, nobjs);
obj* volatile * my_free_list;
obj* result;
obj *current_obj, *next_obj;
//如果只申请到一块内存,将此内存返回即可
if (1 == nobjs){
return chunk;
}
//如果申请到多块内存,将多余的内存挂接到自由链表下,并返回第一个内存块
my_free_list = free_list + FREELIST_INDEX(n);
result = (obj*)chunk;
*my_free_list = next_obj = (obj*)(chunk + n);
int i;
for (i = 1;;i++){
current_obj = next_obj;
next_obj = (obj*)((char*)next_obj + n);
if (nobjs - 1 == i){
current_obj->free_list_link = 0;
break;
}
current_obj->free_list_link = next_obj;
}
return result;
}
template<bool threads,int inst>
void* __DefaultAllocTemplate<threads, inst>::Reallocate(void* p, size_t old_sz, size_t new_sz){
void* result;
size_t copy_sz;
//如果要释放的old_sz和new_sz内存均大于自由链表中可管理的最大内存,说明old_sz内存的申请是通过一级空间配置器Allocate中的malloc直接申请的,
//直接调用realloc进行old_sz的释放和new_sz的申请
if (old_sz > (size_t) __MAX_BYTES && new_sz > (size_t)__MAX_BYTES){
return realloc(p, new_sz);
}
//如果old_sz和new_sz均小于__MAX_BYTES,且ROUND_UP对齐后的内存相同,说明old_sz和new_sz对应的实际内存一样,所以不用重新分配
if (ROUND_UP(old_sz) == ROUND_UP(new_sz)){
return p;
}
//如果old_sz和new_sz在__MAX_BYTES两侧,申请新内存释放旧内存
result = Allocate(new_sz);
//计算要拷贝数据的大小
copy_sz = new_sz > old_sz ? old_sz : new_sz;
memcpy(result, p, copy_sz);
//调用二级空间配置器进行旧内存的释放工作
Deallocate(p, old_sz);
return result;
}
//利用是否定义__USE_MALLOC宏来决定是否使用二级空间配置器
#ifdef __USE_MALLOC
typedef __MallocAllocTemplate<0> alloc;
#else
typedef __DefaultAllocTemplate<false, 0> alloc;
#endif
//更上一次的封装,标准界面
template<class T, class Alloc = alloc>
class SimpleAlloc{
public:
static T* Allocate(size_t n){
return 0 == n ? 0 : (T*)Alloc::Allocate(n * sizeof(T));
}
static T* Allocate(){
return (T*)Alloc::Allocate(sizeof(T));
}
static void Deallocate(T* p, size_t n){
if (0 != n){
Alloc::Deallocate(p, n * sizeof(T));
}
}
static void Deallocate(T* p){
Alloc::Deallocate(p, sizeof(T));
}
};