一、总述
一般而言,我们所习惯的C++内存的配置和释放是这样的:
class Foo {
//定义
};
Foo *pf = new Foo;
delete pf;
其中new操作包含两个步骤:(1)调用operator new配置内存;(2)调用Foo()构造对象内容
delete算式也包括两个步骤:(1)调用~Foo()将对象析构;(2)调用operator delete
为了精密分工,stl将这两个步骤区分开来,内存配置操作由allocate()负责,内存释放操作由deallocate()负责;
对象构造操作由construct()负责,对象析构操作由destroy()负责。
stl的空间配置器本质上来说就是为了实现这四个函数。
二,construct和destroy的实现
这两个函数在stl_construct.h中实现,代码注释如下:
//construct函数,构造p指向的内存对象
template <class T1, class T2>
inline void construct(T1* p, const T2& value) {
new (p) T1(value);
}
//destroy函数,析构pointer指向的内存对象
template <class T>
inline void destroy(T* pointer) {
pointer->~T();
}
//下面三个函数用来实现第一个destroy函数的重载版本
template <class ForwardIterator>
inline void
__destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) {
for ( ; first < last; ++first)
destroy(&*first); //调用第一个destroy函数
}
template <class ForwardIterator>
inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) {}
//根据T类型是否含有trival destructor,调用不同的__destroy_aux重载函数
//trival destrutor的定义
//http://stackoverflow.com/questions/8190879/what-is-a-non-trivial-destructor-in-c
template <class ForwardIterator, class T>
inline void __destroy(ForwardIterator first, ForwardIterator last, T*) {
typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;
__destroy_aux(first, last, trivial_destructor());
}
//重载的destroy版本
template <class ForwardIterator>
inline void destroy(ForwardIterator first, ForwardIterator last) {
__destroy(first, last, value_type(first));
}
//特化的destroy函数
inline void destroy(char*, char*) {}
inline void destroy(wchar_t*, wchar_t*) {}
三、allocate和deallocate的实现
stl定义了两个类模板,实现了两种allocate和deallocate,称为双层级配置器。第一级配置器直接使用malloc()和free(),第二级配置器则视情况采取不同的策略:
当配置区块超过128bytes时,视之为“足够大”,便调用第一级配置器;当配置区块小于128bytes时,视之为“过小”,为了降低额外负担,便采取复杂的memory pool
整理方式,而不再求助于第一级配置器。
1,stl第一级配置器
SGI STL第一级配置器
template<int inst>
clas __malloc_alloc_template { ... };
其中:
1,allocate()直接使用malloc()。
2,模拟c++的set_new_handler()以处理内存不足的情况
//第一级配置器,直接调用malloc和free向堆空间申请释放内存
//需要多少空间就malloc多少
#if 0
#include <new>
#define __THROW_BAD_ALLOC throw bad_alloc
#elif !defined(__THROW_BAD_ALLOC)
#include <iostream.h> //.h的头文件没有定义命名空间
#define __THROW_BAD_ALLOC cerr << "out of memory" << endl; exit(1)
#endif
template <int inst>
class __malloc_alloc_template {
private://下面三个函数处理内存不足的情况
static void *oom_malloc(size_t);
static void *oom_realloc(void *, size_t);
static void (* __malloc_alloc_oom_handler)(); //
public:
static void *allocate(size_t n) //分配内存,直接调用malloc
{
void *result = malloc(n);
if (0 == result)
result = oom_malloc(n); //调用oom_malloc,该函数会循环调用内存不足处理函数(__malloc_alloc_oom_handler)
return result;
}
static void deallocate(void *p, size_t /* n */) //释放内存,直接调用free
{
free(p);
}
static void *reallocate(void *p, size_t /* oldsize */, size_t newsize) //直接调用realloc
{
void *result = realloc(p, newsize);
if (0 == result)
result = oom_realloc(p, newsize); //oom_realloc会循环调用处理函数(__malloc_alloc_oom_handler)
return result;
}
//仿真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_oom_handler<inst>::oom_malloc(size_t n)
{
void (*my_malloc_handler)();
void *result;
for (;;) {
my_malloc_handler = __malloc_alloc_oom_handler;
if (0 == my_malloc_handler)
__THROW_BAD_ALLOC; //宏定义
(*my_malloc_handler)();
result = malloc(n);
if (0 != result)
return result;
}
}
template <int inst>
void * __malloc_alloc_template<inst>::oom_realloc(void *p, size_t newsize)
{
void (*my_malloc_handler)();
void *result;
for (;;) {
my_malloc_handler = __malloc_alloc_oom_handler;
if (0 == my_malloc_handler)
__THROW_BAD_ALLOC;
(*my_malloc_handler)();
result = realloc(p, newsize);
if (0 != result)
return result;
}
}
//实例化一个类模板
typedef __malloc_alloc_template<0> malloc_alloc;
2,stl第二级配置器
SGI STL第二级配置器
template <bool threads, int inst>
class __default_alloc_template { ... };
其中:
1.维护16个自由链表,负责16种小型区块的次配置能力。
内存池以malloc配置而得。如果内存不足,转调用第一级配置器。
2.如果需求区块大于128bytes,就转调用第一级配置器。
stl第二级配置器含有两个关键结构体:空闲链表(free-list)和内存池。
为了方便管理,SGI第二级配置器会主动将任何小额区块的内存需求量上调至8的倍数,并维护16个free-lists,各自管理大小分别为
8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128bytes的小额区块,如图所示:
内存池结构如下图所示:
内存分配步骤如下:
1,如果要分配的内存大于128bytes,直接调用第一级配置器的allocate(nbytes)(malloc_alloc::allocate),返回结果
2,否则寻找nbytes对应的空闲链表下标,并判断该数组元素是否为空,
如果不为空,返回第一块内存,并修改该数组项的值,如下图所示:
如果为空,则从内存池中分配出一块内存,放入对应的数组项中,然后返回第一块内存,如下图所示:
变为
3,如果内存池也为空,则调用malloc为内存池分配空间,继续执行第2步。
4,如果第三歩的malloc出错,则调用第一级配置器的malloc出错处理函数。
内存分配完成。
内存释放步骤如下:
1,如果要释放的内存大小超过128bytes,调用第一级配置器的deallocate
2,否则,根据要释放内存的大小,加入到合适的空闲链表中,如下图所示:
变为:
明白了分配和释放内存的思路,代码不难看懂。
三、stl源码中如何使用配置器
stl源码中不直接使用第一级配置器或第二级配置器的名字,而是使用一个别名alloc,如果定义了__USE_MALLOC宏,则alloc是第一级配置器的别名,
否则alloc是第二级配置器的别名。
#ifdef __USE_MALLOC
typedef __malloc_alloc_template<0> malloc_allloc;//第一级配置器
typedef malloc_alloc alloc;
#else
typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc; //第二级配置器
stl还为alloc定义了一个包装类simple_alloc
template<class T, class Alloc>
class simple_alloc {
public:
static T* allocate(size_t);
static T* allocate(void);
static void deallocate(T*, size_t);
static void deallocate(T*);
};
simple_alloc仅仅只是一个包装类,他内部直接调用alloc的对应函数。
下面来看vector 和 deque的实际运用方式:
template <class T, class Alloc=alloc> //默认配置器是alloc
class vector {
typedef simple_alloc<T, Alloc> data_alloctor;
data_alloctor::allocate(n); //配置n个元素
};
template<class T, class Alloc=alloc, size_t BufSiz=0>
class deque {
typedef simple_alloc<T, Alloc> data_alloctor;
typedef simple_alloc<T*, Alloc> map_alloctor;
data_alloctor::allocate(n);//配置n个元素
};
实际使用的是simple_alloc,simple_alloc会调用alloc,而alloc可能是第一级配置器,也可能是第二级配置器。
所以最终起作用的还是第一级配置器或第二级配置器。