1. 什么是空间配置器
空间配置器,顾名思义就是为各个容器高效的管理空间(空间的申请与回收)的,在默默地工作。虽然在常规使用STL时,可能用不到它,但站在学习研究的角度,学习它的实现原理对我们有很大的帮助。
2. 为什么需要空间配置器
前面在模拟实现vector、list、map、unordered_map等容器时,所有需要空间的地方都是通过new申请的,虽然代码可以正常运行,但是有以下不足之处:
1.空间申请与释放需要用户自己管理,容易造成内存泄漏
2.频繁向系统申请小块内存块,容易造成内存碎片
3.频繁向系统申请小块内存,影响程序运行效率
4.直接使用malloc与new进行申请,每块空间前有额外空间浪费
5.申请空间失败怎么应对
6.代码结构比较混乱,代码复用率不高
7.未考虑线程安全问题
因此需要设计一块高效的内存管理机制。
3. SGI-STL空间配置器实现原理
以上提到的几点不足之处,最主要还是:频繁向系统申请小块内存造成的。那什么才算是小块内存?SGI-STL以128作为小块内存与大块内存的分界线,将空间配置器其分为两级结构,一级空间配置器处理大块内存,二级空间配置器处理小块内存。这篇文章会介绍一级空间配置器的实现,一级与STL容器的结合。二级空间配置器后续会补充。
一级空间配置器原理非常简单,直接对malloc与free进行了封装,并增加了C++中set_new_handle思想。
简单介绍一下set_new_handler:
set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用。
C/C++内存分配采用new和delete。在new申请内存时,可能会遇到的一种情况就是,内存不够了,这时候会抛出out of memory的异常。有的时候,我们希望能够调用自己定制的异常处理函数,这就是本条款要说的。在声明于的一个标准程序库中,有如下的接口:
1 namespace std
2 {
3 typedef void (*new_handler)();
4 new_handler set_new_handler(new handler p) throw();
5 }
注意这里面typedef了一个函数指针new_handler,它指向一个函数,这个函数的返回值为void,形参也是void。set_new_handler就是将new_handler指向具体的函数,在这个函数里面处理out of memory异常(函数末尾的throw()表示它不抛出任务异常),如果这个new_handler为空,那么这个函数没有执行,就会抛出out of memory异常。
1 void MyOutOfMemory()
2 {
3 cout << "Out of memory error!" << endl;
4 abort();
5 }
6
7 int main()
8 {
9 set_new_handler(MyOutOfMemory);
10 int *verybigmemory = new int[0x1fffffff];
11 delete verybigmemory;
12 }
这里预先设定好new异常时调用的函数为MyOutOfMemory,然后故意申请一个很大的内存,就会走到MyOutOfMemory中来了。
注意:第一级配置器的实质是使用了malloc、realloc、free。并处理了内存不足(请求内存失败)的情况,因为::operator new提供了set_new_handler操作,使用户可以自己定制发生内存不足时的处理策略。SGI STL并不能使用C++的set_new_handler,所以单独实现了这个功能。
下面给出一级空间配置器的实现:
template<int inst>
class _malloc_alloc_template
{
/* oom_alloc为静态函数成员,用于处理malloc时的内存不足问题
oom_realloc为静态函数成员,用于处理realloc时的内存不足问题
_malloc_alloc_handler为静态数据成员,为void(*)()类型的函数指针,用于
//用户自己制定内存分配策略
*/
static void * oom_malloc(size_t);//out_of_memmory malloc
static void * oom_realloc(void *, size_t);
static void(*_malloc_alloc_oom_handler)();
public:
static void * allocate(size_t n)
{
void * result = malloc(n);//请求内存
if (result == nullptr)//如果内存不足
result=oom_malloc(n);//调用oom_malloc
return result;
}
static void * reallocate(void * p, size_t n)
{
void *result = realloc(n);
if (result == nullptr)
result = oom_realloc(p, n);
return result;
}
static void deallocate(void * p)
{
//使用free函数释放p地址后所分配的内存块
free(p);
}
/*此静态成员函数接受一个void(*)()类型的函数指针作为参数,返回
void(*)()类型的函数指针。其作用为用用户自己定制的内存调度方法替换
_malloc_alloc_handler,由此实现类似C++的set_new_handler方法。
*/
static void(* set_malloc_handler(void(*f)()))()
{
void(*old)() = _malloc_alloc_oom_handler;
_malloc_alloc_oom_handler = f;
return old;
}
};
template<int inst>
void(*_malloc_alloc_template<inst>::_malloc_alloc_oom_handler)() = 0;
template<int inst>
void * _malloc_alloc_template<inst>::oom_malloc(size_t n)
{
void(*my_oom_handler)();
void * result;
//无限循环,直至成功分配内存或用户没有定制内存分配策略
for (;;)
{
my_oom_handler = _malloc_alloc_oom_handler;
if (my_oom_handler == nullptr)//如果用户没有定制内存分配策略
exit(1);
(*my_oom_handler)();//使用用户定制的方法
result = malloc(n);
if (result)
return result;
}
}
template<int inst>
void * _malloc_alloc_template<inst>::oom_realloc(void * p, size_t n)
{
//此函数的设计思路与oom_malloc如出一辙
void(*my_oom_handler)();
void * result;
for (;;)
{
my_oom_handler = _malloc_alloc_oom_handler;
if (my_oom_handler == nullptr)
exit(1);
(*my_oom_handler)();
result = realloc(p,n);
if (result)
return result;
}
}
4.空间配置器的封装和STL容器和空间配置器的结合
SGI两层配置器
#ifdef __USE_MALLOC
typedef malloc_alloc alloc;
typedef malloc_alloc single_client_alloc;
#else
// 二级空间配置器定义
#endif
由于以上的问题,SGI设计了两层的配置器,也就是第一级配置器和第二级配置器。同时为了自由选择,STL又规定了 __USE_MALLOC 宏,如果它存在则直接调用第一级配置器,不然则直接调用第二级配置器。SGI未定义该宏,也就是说默认使用第二级配置器。
通过预处理命令在第一级或第二级配置器之间选择一个命名为alloc,再为类模板设置第二个模板类型参数:
template<typename T,class Alloc=alloc>
class vector
{
...
};
因为我们将_malloc_alloc_template或_default_alloc_template设置为底层的空间配置器,所以我们还需要设计一个包装函数使其符合常规的空间配置器的使用方式,还可以对底层实现进行更好的封装:
/*
simple_alloc为底层的内存分配类的外部包装,其成员全部调用_malloc_alloc_template
的成员
*/
template<typename T,class Alloc=alloc>
class simple_alloc
{
public:
static T * allocate(void)
{
return (T *)Alloc::allocate(sizeof(T));
}
static T * allocate(size_t n)
{//此allocate接受一个指定对象个数的参数n
return n == 0 ? nullptr : (T *)Alloc::allocate(sizeof(T)*n);
}
static void deallocate(T * p)
{
Alloc::deallocate(p, sizeof(T));
}
static void deallocate(T * p, size_t n)
{
if (n != 0)
Alloc::deallocate(p);
}
};
这样我们便可以这样使用它们:
template<typename T,class Alloc=alloc>
class vector
{
typedef T value_type;
protected:
typedef simple_alloc<value_type, Alloc> data_allocator;
...
void deallocate()
{
if (...)
data_allocator::deallocate(start, end_of_storge - start);
}
...
};