前两天把stl的 空间配置器的思路搞懂了,今天就把一二级配置器自己实现了一番, 简直爽,源码果然博大精深,只可惜我道行不够,实现了好久,才把个中原理搞清楚一些。
现在就开始聊聊空间配置器吧
首先来说说一级空间配置器:
一级是最简单的了,其实就是封装了malloc和free以及realloc三大函数,不过作者有什么比较心思细微的地方呢?
来看看源码剖析一下
typedef void (*FUNC)();
template <int inst>
class __malloc_alloc_template
{
private:
//未分配成功处理函数
static void *oom_malloc(size_t);
static void *oom_realloc(void *,size_t);
//个人定制函数,用于释放空间
static FUNC __malloc_alloc_oom_handler;
public:
static void *allocate(size_t n)
{
void *result = NULL;
result = malloc(n);
if(result == NULL){
result = oom_malloc(n);
}
return result;
}
static void *reallocate(void *p,size_t n)
{
void *result = NULL;
result = realloc(p,n);
if(result == NULL){
result = oom_realloc(p,n);
}
return result;
}
static void *deallocate(void *p, size_t n)
{
free(p);
}
//返回之前的__malloc_alloc_oom_handler函数,把新函数给__malloc
static FUNC set_malloc_handler(FUNC f)
{
FUNC old;
old = __malloc_alloc_oom_handler;
__malloc_alloc_oom_handler = f;
return old;
}
};
我只拷贝了一部分,加黑的如果了解了那么一级配置器就搞定了,首先说说看这个oom_malloc,当malloc失败时,这个函数就被调用。它的作用就是去调用用户自定义的释放空间的一个函数__malloc_alloc_oom_handler,不断的去释放空间,直到可以申请空间为止,如果实在不成功就会抛出异常,结束程序
oom_realloc和oom_malloc大同小异,几乎是一样的。惟一的区别在于前者是realloc申请空间,后者是malloc申请空间
接下来有意思的来了,可能很多看过stl源码剖析的同学(其实是我开始不懂啦- -)会对 void (*set_malloc_handler(void (*f)()))()产生疑问,妈的这时什么!!
好,现在我来解答这个疑问,看到typedef void (*FUNC)() 了么, FUNC是一种类型,它定义的变量是指向函数的指针,如果我把上面的函数写成这样是不是就比较好看懂了呢 static FUNC set_malloc_handler(FUNC f) 说白了, 就是一个函数它的参数是个指针,它的返回值也是个指针,就这样!!!
如果细一点说,那么参数和反回值都是指向函数的指针。懂了么~~
现在聊聊第二步,二级配置器
二级配置器就是在申请128字节以下会用到的东东。
来一张图片,对着图片详细说说咱们的二级空间配置器
针对这张图,首先来解释一下 横行那些是代表这一个空闲的数组(总共有16个元素,我没有画全),每一个元素都是一个指针。
其中这个空闲数组的每一个元素指向了它们自己对应的空闲链表。
每一条空闲链表的每一块是根据对应 (数组下标+1)*8 作为一块的大小。
而start_free和end_free是多划分出的空闲区,并不属于哪个链表中。这一点稍后去说。
其实这个数据结构让我看来有种 哈希表的味道,啧啧。真是有点美味啊!!
其实这个数组是没有什么好说的,比较让人觉得制作新奇且高效的是在挂链表的节点操作上。
下面来说说
union obj
{
union obj *free_list_link;
char client_data[1];
};
可能有些同学不知道为什么用这个联合体,我来详细解释一下
这个联合体是在节点内部的,这样做的目的是节省了额外空间的开辟。那么肯定有同学就不了解既然在节点内部,拿节点的值不是把它覆盖了么,那怎么可能去保存下一个节点的位置
确实联合体的内容会被覆盖,但是被覆盖前它就已经去让空闲链表的指针指向下一个节点了,所以这是它精妙之处
恩~~两步就解决了
为了解决方便我自定义一个链表的首节点好了 union *list 链表的节点大小设置为n
接下来需要有两个辅助的指针,一个是obj *cur一个是 obj *next;
//先让next和cur存储为链表头节点地址
next = cur = list;
//因为链表地址空间连续,所以让next+n
next = (obj *)((char *)next + n);
//然后让cur的下一个节点指向next
cur->free_list_link = next;
如何回收呢?
回收的方法是这样的
根据传入的指针p和用的空间n
obj *l;
l = (obj *)p;
l->next = list;
list = l;
给一个图应该比较好懂一些
list指向的空闲链表开始的地方,p指向的是要释放的空间,目的就是让list指向p指向的位置就是这样,还有多的一步就是要把节点挂到链上。
现在可以讲讲空闲区间的问题了
空闲区间有3种去留:(1).就是空闲区间
(2).被分配出去
(3).如果空闲区间不够要分配的空间,那么就会被挂到对应的链表上
大概就是这样,大体上就是这些,我下面把代码贴上来,可以运行的。
//二级空间配置器
enum{__ALIAN = 8}; //边界
enum{__MAX_BYTES = 128}; //最大分配块
enum{__NFREELISTS = __MAX_BYTES / __ALIAN};//空闲链表个数
template <bool threads, int inst>
class __default_alloc_template
{
public:
static void *allocate(size_t n)
{
//一级的指针不能被编译器优化,因为申请空间时会迭代去挂链
//很有可能编译器把它优化到寄存器中
obj *volatile *my_free_list;
obj *result;
if(n > (size_t)__MAX_BYTES){
return (malloc_alloc::allocate(n));
}
my_free_list = free_list + FREELIST_INDEX(n);
result = *my_free_list;
if(0 == result){
//没有找到freelist
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){
malloc_alloc::deallocate(p,n);
return;
}
my_free_list = free_list + FREELIST_INDEX(n);
//让my_free_list往前指一个单位,q->next = p; p = q;
q->free_list_link = *my_free_list;
*my_free_list = q;
}
static void *reallocate(size_t n);
private:
static size_t ROUND_UP(size_t n)
{
//如果二进制的后三位不为0,那么就去进位,然后和7的反相与,去除后三位
//是8的倍数
return (n+__ALIAN-1) & ~(__ALIAN-1);
}
private:
union obj
{
union obj *free_list_link; //指向下一个节点
char first_block[1]; //下一个节点的首地址的内容
};
private:
static obj *volatile free_list[__NFREELISTS];
static size_t FREELIST_INDEX(size_t n)
{
return ROUND_UP(n) / __ALIAN - 1;
}
//free_list没有可用块时调用refill
static void *refill(size_t n)
{
int nobjs = 20;
char *chunk = chunk_alloc(n, nobjs);
obj *volatile *my_free_list;
obj *result;
obj *current_obj, *next_obj;
int i;
if(1 == nobjs)
return chunk;
my_free_list = free_list + FREELIST_INDEX(n);
result = (obj *)chunk;
//引导free list指向新配置的空间
*my_free_list = next_obj = (obj *)(chunk + n);
//不断的去挂链
for(i = 0; ; ++i){
current_obj = next_obj;
next_obj = (obj *)(next_obj + n);
//最后一块就让它置空
if(nobjs - 1 == i){
current_obj->free_list_link = 0;
break;
}
current_obj->free_list_link = next_obj;
}
return result;
}
//配置一大块
static char *chunk_alloc(size_t size, int &nobjs)
{
char *result;
size_t total_bytes = size * nobjs;
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 = nobjs * size;
result = start_free;
start_free += size;
return result;
}else{
//内存池连一块大小都不能提供
size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
//把多出的剩余空间挂到空闲链表上
if(bytes_left > 0){
obj *volatile *my_free_list = free_list + FREELIST_INDEX(bytes_left);
*my_free_list = (obj *)start_free;
}
start_free = (char *)malloc(bytes_to_get);
if(0 == start_free){
int i;
obj *volatile *my_free_list, *p;
//去空闲链表找
for(i = size; i < __MAX_BYTES; i += __ALIAN){
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;
return (chunk_alloc(size, nobjs));
}
}
//还没找到
end_free = 0;
start_free = (char *)malloc_alloc::allocate(bytes_to_get);
}
heap_size += bytes_to_get;
end_free = start_free + bytes_to_get;
return chunk_alloc(size, nobjs);
}
}
static char *start_free;
static char *end_free;
static size_t heap_size;
};
template <bool threads, int inst>
char *__default_alloc_template<threads, inst> :: start_free = 0;
template <bool threads, int inst>
char *__default_alloc_template<threads, inst> :: end_free = 0;
template <bool threads, int inst>
size_t __default_alloc_template<threads, inst> :: heap_size = 0;
//为什么要写两次
template <bool threads, int inst>
//obj *volatile是类型,并且该类型属于类中
//typename __default_alloc_template<threads, inst> :: obj* volatile
union a
{
};
a *volatile __default_alloc_template<threads, inst> :: free_list[__NFREELISTS] =
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
typedef __default_alloc_template<0,0> alloc;