1.volatile关键字
2.一开始时,自由链free_list都为空,当链表为空时,要进行填充,调用refill函数进行填充
3.内存池只能解决外部碎片,但是不能解决内部碎片,假如我需要申请20字节的空间,那么只能将24字节的内存分配给我,其中20字节供我使用,剩下的4字节就浪费了,就是块内碎片,剩下的4个字节是不会链接到其他区域的链表上的,因为最小内存块的大小是8字节
内存碎片是无法克服的问题,理论上外部碎片和内碎片是无法同时解决的
4.在自由链中的这些内存块还是属于内存池,而不属于用户,所以内存池可以自行调整
5.我们希望一下子可以填充20个块,如果成功申请下来了20个块,其中一块供用户使用,剩下19个块链接到自由链对应的区域中
如果申请下来了不够20个块,但是大于一个块也行,其中第一块供用户使用,其他块链接到自由链中
6.模拟申请空间时空间不足时一级配置器的处理方式
首先营造一个在申请空间之前,已经从堆区申请了很大的一块内存,然后此时调用自己命名空间的yhp::malloc_alloc::allocate函数
char* cpa = (char*)malloc(sizeof(char) * 100000000);
void fun()
{
if (cpa != nullptr)
{
free(cpa);
}
cpa = nullptr;
yhp::malloc_alloc::set_malloc_handler(nullptr);
}
int main()
{
yhp::malloc_alloc::set_malloc_handler(fun);
int* p = (int*)yhp::malloc_alloc::allocate(sizeof(int) * 100);
return 0;
}
首先调用PFUN set_malloc_handler(PFUN p)函数,__malloc_alloc_oom_handler指向fun函数,然后调用一级配置器中的allocate函数,实际上系统仍然可以分配我们需要的空间,但是为了演示内存不足的情况,我将result的值置为0,来表明空间不足以分配了
此时,申请空间失败了,调用oom_malloc函数来继续申请空间,由于__malloc_alloc_oom_handler不为空(指向fun函数),所以my_malloc_handler也不为空,所以会调用my_malloc_handler()函数,也就是fun函数,将cpa指向的空间释放掉,然后调用set_malloc_handler函数,将__malloc_alloc_oom_handler倒换成nullptr,这就在以后如果申请空间不足时,不能再调用fun函数释放空间了,因为之前已经释放过了,然后result = malloc(n);继续申请空间,如果申请成功了,返回申请的空间
假如我在调用完fun函数释放完空间以后,仍然不足够我开辟空间,那么会再次进入for循环,此时my_malloc_handler==nullptr,调用宏__THROW_BAD_ALLOC抛出异常或者终止程序
7.模拟二级配置器申请空间时的情况
int main()
{
yhp::my_list<int> mylist;
return 0;
}
先调用mylist的构造函数,然后调用_Buynode函数,然后调用data_allocate::allocate函数,也就是二级配置器的allocate函数,来申请1个T类型的空间,T是一个_Node类型,在重定义data_allocate的时候设置的,然后调用一级配置器的allocate函数,此时size的大小是12字节,12小于128字节,就用二级配置器,如果大于128字节,就调用一级配置器,然后调用FREELIST_INDEX获取几号区域的下标,my_free_list指向下标为1的区域(1区域一个内存块为16个字节),result指向一号区域的第一个内存块,由于此时自由链中还没有内存块,所以都为NULL
然后进行填充,填充之前调用ROUND_UP函数,将12向上取整为8的倍数,即16,然后调用refill函数填充,一次填充20个块,然后调用chunk_alloc函数,总共需要的字节数total_bytes为2016=320个字节,此时池子剩余的字节数bytes_left为0,无法分配,执行size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);希望获取了一个3202=640字节的空间bytes_to_get,start_free指向新空间的开头,end_free = start_free + bytes_to_get;end_free指向新空间的末尾,此时内存池的大小heap_size为640字节,再调用chunk_alloc函数,此时640大于需要开辟的320,result指向池子的开头,start_free = start_free + total_bytes;start_free指向池子的中间,这一块就是申请的空间,然后回到refill函数,此时chunk指向这块空间,my_free_list指向一号区域,next_obj指向的就是第二个内存块(一共1号区域分了20个内存块),*my_free_list也指向第二个内存块,然后将19个内存块以此链接到1号区域中,result指向供给用户使用的内存块,refill填充函数结束时,将result返回
回到_Buynode函数中,此时_S就是指向用户使用的内存块,然后返回到main函数中,结束
8.如果_Node的节点大小大于128,则直接调用一级配置器
9.一二级配置器代码及注释
#ifndef MY_ALLOC_H
#define MY_ALLOC_H
#include<iostream>
//配置器
namespace yhp
{
#if 0
#include<new>
#define __THROW_BAD_ALLOC throw std::bad_alloc;
#elif !defined(__THROW_BAD_ALLOC)
#define __THROW_BAD_ALLOC std::cerr<<"out of memory"<<std::endl; exit(1)
#endif
//一级空间配置器
template<int inst>
class __malloc_alloc_template
{
public:
using PFUN = void (*)();//PFUN是一个函数指针
private:
//当调用allocate申请空间内存不足时,调用oom_malloc
static void* oom_malloc(size_t n)
{
void* result = NULL;
void (*my_malloc_handler) () = nullptr;
for (;;)
{
my_malloc_handler = __malloc_alloc_oom_handler;
if (nullptr == my_malloc_handler)//如果没有空间了
{
__THROW_BAD_ALLOC;//调用宏,抛出异常或者终止掉
}
my_malloc_handler();//释放出更多的内存
result = malloc(n);//再一次申请空间
if (nullptr != result)//申请成功了,返回申请空间的指针
{
return result;
}
}
}
//当调用reallocate扩容,空间不足时,调用oom_realloc
static void* oom_realloc(void* p, size_t new_sz)
{
void* result = NULL;
void (*my_malloc_handler) () = nullptr;
for (;;)
{
my_malloc_handler = __malloc_alloc_oom_handler;//如果set_malloc_handler传进来的指针不为空,
//说明还有空间
if (nullptr == my_malloc_handler)
{
__THROW_BAD_ALLOC; //调用宏,抛出异常或者终止程序
}
my_malloc_handler();//释放出更多的内存
result = realloc(p, new_sz);//再一次申请空间
if (nullptr != result)//申请成功了,返回申请空间的指针
{
return result;
}
}
}
//主要解决内存不足的问题
static PFUN __malloc_alloc_oom_handler;
public:
//申请n个空间,不管是不是大于128,都不关心
static void* allocate(size_t n)
{
void* result = malloc(n);//申请n个空间
if (nullptr == result)//如果申请失败了
{
result = oom_malloc(n);//调用oom_malloc
}
return result;//如果没有失败就返回申请的空间的指针
}
//释放空间
static void deallocate(void* p, size_t n)
{
free(p);
}
//实际上size_t old_sz这个参数并没有用到,reallocate用来扩容
static void* reallocate(void* p, size_t old_sz, size_t new_sz)
{
void* result = realloc(p, new_sz);//申请空间
if (nullptr == result)//申请空间失败了
{
result = oom_realloc(p, new_sz);//调用oom_realloc,重新获取空间
}
return result;
}
//处理内存不足的情况
//传进来一个函数指针p,p指向的函数可以释放之前申请的内存,并重新调用set_malloc_handler(nullptr)这个函数
//此时传进来为nullptr,表明之前申请的内存已经释放了
static PFUN set_malloc_handler(PFUN p)//进行了数据的倒换
{
PFUN old = __malloc_alloc_oom_handler;
__malloc_alloc_oom_handler = p;
return old;
}
};
//初始化__malloc_alloc_oom_handler指针
template<int inst>
typename __malloc_alloc_template<inst>::PFUN
__malloc_alloc_template<inst>::__malloc_alloc_oom_handler = nullptr;
//一级配置器类型重命名
//typedef __malloc_alloc_template<0> malloc_alloc;
using malloc_alloc = __malloc_alloc_template<0>;//给这个0就意味着我们要实例化出这个类型
//二级配置器
enum { __ALIGN = 8 };
enum { __MAX_BYTES = 128 };
enum { __NFREELISTS = __MAX_BYTES / __ALIGN }; // 16
template<bool threads, int inst>
class __default_alloc_template
{
private:
union obj
{
union obj* free_list_link; // 相当于next域
//char client_data[1];
};
private:
static obj* volatile free_list[__NFREELISTS];//0~15号链表数组
static char* start_free;//内存池开始
static char* end_free;//内存池结束
static size_t heap_size; //从堆区获取的总空间的大小
//向上取整到8的倍数
static size_t ROUND_UP(size_t bytes)
{
return (bytes + __ALIGN - 1) & ~(__ALIGN - 1);//__ALIGN - 1设是7,7的二进制是0000 0111,取反是1111 1000
//1111 1000和(bytes + __ALIGN - 1)按位与的结果就是向上取整到8的倍数
}
//得到0~15号链表的下标
static size_t FREELIST_INDEX(size_t bytes)
{
return (bytes + __ALIGN - 1) / __ALIGN - 1;
}
static char* chunk_alloc(size_t size, int& nobjs)
{
char* result = NULL;
size_t total_bytes = size * nobjs;//总共需要的字节数
size_t bytes_left = end_free - start_free;//池子所剩的字节数
if (bytes_left >= total_bytes)//池子的容量够用
{
result = start_free;//指向池子的开始
start_free = start_free + total_bytes;//指向池子某一个位置
return result;//返回指向池子开头的指针
}
else if (bytes_left >= size)//池子的容量不能全部满足,但是能满足一部分
//能不能分一个块?如果比一个块要大,分几个块也行,不一定就是20个块
{
nobjs = bytes_left / size;//此时nobjs就是分的块的个数
total_bytes = size * nobjs;//需要的总大小
result = start_free;//指向池子的开始
start_free = start_free + total_bytes;//指向池子某一个位置
return result;//返回指向池子开头的指针
}
else//内存池连一个块都无法分配,但是此时bytes_left肯定是8的倍数
{
size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);//希望获得的空间是原来的2倍并加上一个调整值
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);//start_free指向新开辟的空间
if (NULL == start_free)//申请为空,没有从堆区申请到空间
{
//堆区没有空间了,依次看后面的区域有没有空间
obj* volatile* my_free_list = NULL;
obj* p = NULL;
for (int i = size; i <= __MAX_BYTES; i += __ALIGN)
{
my_free_list = free_list + FREELIST_INDEX(i);
p = *my_free_list;//指向FREELIST_INDEX(i)区域的第一个节点
if (NULL != p)//找到了
{
*my_free_list = p->free_list_link;//该区域指针my_free_list指向第二个节点
start_free = (char*)p;//指向申请出来的节点的开头
end_free = start_free + i;//指向申请出来的节点的尾部
return chunk_alloc(size, nobjs);//再次调用chunk_alloc函数
}
}
//如果走到这里说明堆区申请不出来了,自由链也没有了,都为空,走头无路了,就调用一级配置器
//如果说my_malloc_handler不为空,就调用释放空间函数,如果my_malloc_handler也为空,说明是真的走投无路了
//要么退出程序,要么提示out of memory
start_free = (char*)malloc_alloc::allocate(bytes_to_get);
}
//不等于空,说明从堆区获得了空间
end_free = start_free + bytes_to_get;//end_free指向新开辟空间的某个位置
heap_size += bytes_to_get;//新从堆区获得的大小
return chunk_alloc(size, nobjs);//继续进入该函数
}
}
static void* refill(size_t size)//size是此时是8的倍数
{
int nobjs = 20;//填充20个块
char* chunk = chunk_alloc(size, nobjs);//注意nobjs是以引用的方式传进去的
if (1 == nobjs)//如果只给了一个内存块,也够用,就供给用户使用
{
return chunk;
}
//核心代码
obj* volatile* my_free_list = NULL;
obj* result = (obj*)chunk;//块指针
obj* current_obj = NULL;
obj* next_obj = NULL;
int i = 0;
my_free_list = free_list + FREELIST_INDEX(size);//my_free_list指向FREELIST_INDEX(size)号链表的开头
*my_free_list = next_obj = (obj*)(chunk + size);//*my_free_list和next_obj都指向第二个节点的开始位置
for (i = 1; ; ++i)//将nobjs-1块内存链接进去
{
current_obj = next_obj;//current_obj指向next_obj所指向的位置
next_obj = (obj*)((char*)next_obj + size);//next_obj指向下一个块的开始位置
//这样一来current_obj指向上个节点的开始位置,next_obj指向本节点的开始位置
if (i == nobjs - 1)//说明是最后一块
{
current_obj->free_list_link = NULL;
break;
}
current_obj->free_list_link = next_obj;
}
return result;
}
public:
//申请空间
static void* allocate(size_t size)
{
if (size > (size_t)__MAX_BYTES)//如果大于128字节
{
return malloc_alloc::allocate(size);//调用一级配置器的allocate函数
}
//小于等于128
obj* result = nullptr;
obj* volatile* my_free_list = nullptr; //二级指针
my_free_list = free_list + FREELIST_INDEX(size);//my_free_list指向某号链表的开头
result = *my_free_list;//result指向size对应区域的第一个内存块,就是供用户使用的内存块
if (nullptr == result)//如果没有内存块,即内存不足
{
void* r = refill(ROUND_UP(size));//填充链表
return r;
}
//如果还有内存块,就分配
*my_free_list = result->free_list_link;//*my_free_list指向下一块内存
return result;//返回第一块内存
}
//销毁,将p指向的空间还给内存池,n是p所指向的空间的大小
static void deallocate(void* p, size_t n)
{
//如果大于128,转到一级配置器
if (n > (size_t)__MAX_BYTES)
{
malloc_alloc::deallocate(p, n);
return;
}
//小于等于128,二级配置器释放
obj* q = (obj*)p;
obj* volatile* my_free_list = free_list+FREELIST_INDEX(n); //二级指针
q->free_list_link = *my_free_list;
*my_free_list = q;
return;
}
//非要重要,面试经常考,
static void* reallocate(void* p, size_t old_sz, size_t new_sz)
{
//如果旧的大小和新的大小都大于128,就转到一级配置器去
if (old_sz > (size_t)__MAX_BYTES && new_sz > (size_t)__MAX_BYTES)
{
return malloc_alloc::reallocate(p, old_sz, new_sz);
}
//如果原来的大小和新的大小的区域是一个,不需要再开辟空间了,比如20和22
if (ROUND_UP(old_sz) == ROUND_UP(new_sz))//将旧的大小和新的大小提升到8的倍数
{
return p;
}
size_t sz = old_sz < new_sz ? old_sz : new_sz;//保存较小的空间大小
void* s = allocate(new_sz);//开辟新的空间
memove(s, p, sz);//将原来的空间的数据拷贝到新空间中
deallocate(p, old_sz);//释放原来的空间
return s;
}
};
template<bool threads, int inst>
typename __default_alloc_template<threads, inst>::obj* volatile
__default_alloc_template<threads, inst>::free_list[__NFREELISTS] = {};
template<bool threads, int inst>
char* __default_alloc_template<threads, inst>::start_free = nullptr;
template<bool threads, int inst>
char* __default_alloc_template<threads, inst>::end_free = nullptr;
template<bool threads, int inst>
size_t __default_alloc_template<threads, inst>::heap_size = 0;
/
// 开关语句
//如果没有定义__USE_MALLOC,那么默认使用二级配置器
//如果定义了__USE_MALLOC,就是用一级配置器
#ifdef __USE_MALLOC
typedef __malloc_alloc_template<0> malloc_alloc
typedef malloc_alloc_alloc;
#else
typedef __default_alloc_template<0, 0> alloc;
#endif
template<class T, class Alloc>
class simple_alloc
{
public:
//申请n个T类型空间
static T* allocate(size_t n)//申请n个T类型的空间大小
{
return (T*)Alloc::allocate(sizeof(T) * n);
}
//申请1个T类型空间
static T* allocate()
{
return (T*)Alloc::allocate(sizeof(T));
}
//释放n个T类型空间
static void deallocate(T* p, size_t n)
{
if (NULL == p) return;
Alloc::deallocate(p, sizeof(T) * n);
}
//释放1个T类型空间
static void deallocate(T* p)
{
if (NULL == p) return;
Alloc::deallocate(p, sizeof(T));
}
};
}
#endif