1. STL 的空间配置器
STL 空间配置器在运用的角度来说,是最不需要介绍的,它总是隐藏在一切组件背后。但若以 STL 的实现角度而言,第一个需要理解的就是空间配置器。
根据 STL 规范,以下是 allocator 的必要接口:
allocator::value_type
allocator::pointer
allocator::const_pointer
allocator::reference
allocator::const_reference
allocator::size_type
allocator::difference_type
//一个嵌套的class template.class rebind<U>拥有唯一成员other,那allocator::allocator()
allocator::rebind
//default constructor
allocator::allocator()
//copy constructor
allocator::allocator(const allocator &)
//泛化的copy constructor
template<class U>
allocator::allocator(const allocator<U> &)
//destructor
allocator::~allocator()
//返回某个对象的地址。算式a.address(x)等同于&x
pointer allocator::address(reference x) const
//返回某个const对象的地址。算式a.address(x)等同于&x
const_pointer allocator::address(const_referenc x) const
//配置空间,足以存储n个T对象。第二参数是个提示。实现上可能会利用它来增进区域性,或完全忽略之。
pointer allocator::allocate(size_type n ,const void * = 0 )
//归还先前配置的空间
void allocator::deallocate(pointer p ,size_type n)
//返回可成功配置的最大量
size_type allocator::max_size() const
//等同于new((void *)p) T(x)
void allocator::construct(pointer p ,const T& x)
//等同于p->~T()
void allocator::destory(pointer p)</span>
2. 具备次配置力的 SGI 空间配置器
SGI 的配置器与众不同,也与标准规范不同,其名称是 alloc,而不是 allocator,而且不接受任何参数。SGI 也有定义一个符合部分标准、名为 allocator 的配置器,但 SGI 自己从未用过它,也不建议我们使用,主要原因是效率不佳,只是对 ::operator new 和 ::operator delete 的简单包装。
为了精密分工,STL allocator 决定将 内存配置/释放 和对象构造/析构 两个阶段的操作区分开来,分别由如下函数来实现:
alloc::allocator() //内存配置
alloc::deallocator() //内存配置
::construcct() //对象构造
::destroy() //对象析构
2.1 构造和析构基本工具:construct() 和 destroy()
以下是construct() 和 destroy()的实现:
#ifndef __SGI_STL_INTERNAL_CONSTRUCT_H
#define __SGI_STL_INTERNAL_CONSTRUCT_H
#include <new.h>
__STL_BEGIN_NAMESPACE
// 使用 placement new 构造对象,调用构造函数 T1::T1(value);
template <class T1, class T2>
inline void construct(T1* p, const T2& value) {
new (p) T1(value);
}
// 接受一个指针的 destroy 函数
template <class T>
inline void destroy(T* pointer) {
pointer->~T();
}
// 接受两个迭代器,使用 value_type() 获得迭代器所指对象的数值型别
template <class ForwardIterator>
inline void destroy(ForwardIterator first, ForwardIterator last) {
__destroy(first, last, value_type(first));
}
// 使用 __type_traits<> 判断该类型析构函数是否为 trivial
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());
}
// 对象析构函数为 trivial,什么都不用做
template <class ForwardIterator>
inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) {}
// 对象析构函数为 non-trivial,这时需要对迭代器范围内的每一个元素分别调用 destroy()
template <class ForwardIterator>
inline void
__destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) {
for ( ; first < last; ++first)
destroy(&*first);
}
// destroy() 针对 char* 和 wchar* 的特化版
inline void destroy(char*, char*) {}
inline void destroy(wchar_t*, wchar_t*) {}
__STL_END_NAMESPACE
#endif /* __SGI_STL_INTERNAL_CONSTRUCT_H */
2.2 空间的配置与释放
对象构造前的空间配置和对象析构后的空间释放由 stl_alloc.h 负责,SGI 对此的设计哲学如下:
- 向 system heap 要求空间
- 考虑多线程状态
- 考虑内存不足时的应变措施
- 考虑过多“小型区块”可能造成的内存碎片(fragment)问题
alloc 配置内存和释放内存使用的是 malloc() 和 free()。
考虑到小型区块可能造成内存碎片问题,SGI 设计了双层配置器,第一级配置器直接使用 malloc 和 free,第二级配置器视情况不同采用不同的配置器:
- 当配置区块超过128 byte 时,视为足够大,调用第一级配置器
- 当配置区块小于128 byte 时,视为过小,采用内存池的方式配置内存
只开放第一级配置器还是同时开发第二级配置器取决于 __USE_MALLOC 是否被定义:
无论 alloc 被定义为哪一级配置器,SGI 再为它包装一个接口如下:
template<class T, class Alloc>
class simple_alloc {
public:
static T *allocate(size_t n)
{ return 0 == n? 0 : (T*) Alloc::allocate(n * sizeof (T)); }
static T *allocate(void)
{ 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)); }
};
两级配置器的关系:
SGI STL 容器全部使用这个 simple_alloc 接口,例如:
template <class T, class Alloc = alloc>
class vector {
public:
typedef T value_type;
typedef value_type* pointer;
...
typedef simple_alloc<value_type, Alloc> data_allocator;
iterator start;
iterator finish;
iterator end_of_storage;
...
void deallocate() {
if (start) data_allocator::deallocate(start, end_of_storage - start);
}
2.3 第一级配置器 __malloc_alloc_template
template <int inst>
class __malloc_alloc_template {
private:
// 用于处理内存不足的情况
static void *oom_malloc(size_t);
static void *oom_realloc(void *, size_t);
#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
static void (* __malloc_alloc_oom_handler)();
#endif
public:
// 直接 malloc
static void * allocate(size_t n)
{
void *result = malloc(n);
if (0 == result) result = oom_malloc(n);
return result;
}
// 直接 free
static void deallocate(void *p, size_t /* n */)
{
free(p);
}
// 直接 realloc
static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz)
{
void * result = realloc(p, new_sz);
if (0 == result) result = oom_realloc(p, new_sz);
return result;
}
// 设置自己的 out-of-memory-handler
static void (* set_malloc_handler(void (*f)()))()
{
void (* old)() = __malloc_alloc_oom_handler;
__malloc_alloc_oom_handler = f;
return(old);
}
};
// malloc_alloc out-of-memory handling
#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
template <int inst>
void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0;
#endif
template <int inst>
void * __malloc_alloc_template<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 (result) return(result);
}
}
template <int inst>
void * __malloc_alloc_template<inst>::oom_realloc(void *p, 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 = realloc(p, n); // 再次尝试配置内存
if (result) return(result);
}
}
typedef __malloc_alloc_template<0> malloc_alloc;
第一级配置器的 allocate 和 realloc 都是在调用 malloc 和 realloc 不成功后,改调用 oom_malloc 和 oom_realloc,后两者都有内循环,不断调用内存不足处理例程,期望在某次调用后,获得足够内存而圆满完成任务。若内存不足处理例程未被设定,oom_malloc 和 oom_realloc 便调用 __THROW_BAD_ALLOC抛出 bad_alloc 异常或利用 exit(1) 中止程序。设计和设定内存不足处理例程都是客端的责任。
2.4 第二级配置器 __malloc_alloc_template
enum {__ALIGN = 8};
enum {__MAX_BYTES = 128};
enum {__NFREELISTS = __MAX_BYTES/__ALIGN};
template <bool threads, int inst>
class __default_alloc_template {
private:
static size_t ROUND_UP(size_t bytes) {
return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));
}
private:
union obj {
union obj * free_list_link;
char client_data[1]; /* The client sees this. */
};
private:
static obj * __VOLATILE free_list[__NFREELISTS];
static size_t FREELIST_INDEX(size_t bytes) {
return (((bytes) + __ALIGN-1)/__ALIGN - 1);
}
static void *refill(size_t n);
static char *chunk_alloc(size_t size, int &nobjs);
// 内存池
static char *start_free;
static char *end_free;
static size_t heap_size;
public:
/* n must be > 0 */
static void * allocate(size_t n)
{
obj * __VOLATILE * my_free_list;
obj * __RESTRICT 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 (result == 0) {
void *r = refill(ROUND_UP(n));
return r;
}
*my_free_list = result -> free_list_link;
return (result);
};
/* p may not be 0 */
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);
q -> free_list_link = *my_free_list;
*my_free_list = q;
}
static void * reallocate(void *p, size_t old_sz, size_t new_sz);
} ;
typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc;
typedef __default_alloc_template<false, 0> single_client_alloc;
// 注意:njobs是引用参数
template <bool threads, int inst>
char*
__default_alloc_template<threads, inst>::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 = size * nobjs;
result = start_free;
start_free += total_bytes;
return(result);
} else { // 内存池剩余空间一个区块都无法提供
size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
// Try to make use of the left-over piece.
if (bytes_left > 0) { // 将内存池的残余空间加入到 free_list
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); // 申请堆内存,补充内存池
if (0 == start_free) {
int i;
obj * __VOLATILE * my_free_list, *p;
for (i = size; i <= __MAX_BYTES; i += __ALIGN) {
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;
// 递归调用自己,修正 njobs 并返回
return(chunk_alloc(size, nobjs));
// 注意:任何残余零头终将被编入 free_list中备用
}
}
end_free = 0; // 出现意外,无内存可用
start_free = (char *)malloc_alloc::allocate(bytes_to_get);
// 抛出异常或者内存不足的情况获得改善
}
// 申请堆内存成功,调整内存池
heap_size += bytes_to_get;
end_free = start_free + bytes_to_get;
// 递归调用自己,修正 njobs 并返回
return(chunk_alloc(size, nobjs));
}
}
template <bool threads, int inst>
void* __default_alloc_template<threads, inst>::refill(size_t n)
{
int nobjs = 20;
// 调用 chunk_alloc,尝试取得 njobs 个区块作为 free_list 的新节点
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);
// 准备调整 free_list,加入新节点
my_free_list = free_list + FREELIST_INDEX(n);
// 在 chunk_alloc 中得到的空间构建 free_list 节点
result = (obj *)chunk;
*my_free_list = next_obj = (obj *)(chunk + n);
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;
} else {
current_obj -> free_list_link = next_obj;
}
}
return(result);
}
template <bool threads, int inst>
void*
__default_alloc_template<threads, inst>::reallocate(void *p,
size_t old_sz,
size_t new_sz)
{
void * result;
size_t copy_sz;
// 如果分配区块大小 > 128bytes,直接 realloc
if (old_sz > (size_t) __MAX_BYTES && new_sz > (size_t) __MAX_BYTES) {
return(realloc(p, new_sz));
}
if (ROUND_UP(old_sz) == ROUND_UP(new_sz)) return(p);
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);
}
上面是部分源码,删掉了多线程的那部分代码。为了方便管理,SGI 第二级配置器会主动将任何小额区块的内存需求量上调至 8 的倍数(例如客户端要求30bytes,就自动调整为 32 bytes),并维护16个 free-lists,各自管理大小分别为:8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128 bytes的小额区块。
union obj {
union obj * free_list_link;
char client_data[1]; /* The client sees this. */
};
对于 联合体 obj,可以理解为:
- 当 obj 对象未被用户使用时,作为链表指针存在指向下一个obj,这个时候是不需要内存中的数据的
- 当 obj 对象已被用户使用时,这块内存被分配给用户,client_data用来存数据(client_data 的大小并不是1,而是前面讲的 8 的 16 个整倍数),此时链表指针就没用了
也就是说,每个 obj 在一个时刻只有一个字段有实际意义(free_list_link 或者 client_data),其主要目的是节省内存。
3. 内存基本处理工具
STL 定义有 5 个全局函数,作用于未初始化空间上。前两个是 construct() 和 destroy(),另外 3 个是:
uninitialized_copy()
uninitialized_fill()
uninitialized_fill_n()
它们分别对应于高层次函数 copy()、 fill()、 fill_n(),这些都是 STL 的算法。
uninitialized_copy()、uninitialized_fill()、uninitialized_fill_n() 实际定于于 stl_uninitialized。
// Valid if copy construction is equivalent to assignment, and if the
// destructor is trivial.
template <class InputIterator, class ForwardIterator>
inline ForwardIterator
__uninitialized_copy_aux(InputIterator first, InputIterator last,
ForwardIterator result,
__true_type) {
return copy(first, last, result);
}
template <class InputIterator, class ForwardIterator>
ForwardIterator
__uninitialized_copy_aux(InputIterator first, InputIterator last,
ForwardIterator result,
__false_type) {
ForwardIterator cur = result;
__STL_TRY {
for ( ; first != last; ++first, ++cur)
construct(&*cur, *first);
return cur;
}
__STL_UNWIND(destroy(result, cur));
}
template <class InputIterator, class ForwardIterator, class T>
inline ForwardIterator
__uninitialized_copy(InputIterator first, InputIterator last,
ForwardIterator result, T*) {
typedef typename __type_traits<T>::is_POD_type is_POD;
return __uninitialized_copy_aux(first, last, result, is_POD());
}
template <class InputIterator, class ForwardIterator>
inline ForwardIterator
uninitialized_copy(InputIterator first, InputIterator last,
ForwardIterator result) {
return __uninitialized_copy(first, last, result, value_type(result));
}
inline char* uninitialized_copy(const char* first, const char* last,
char* result) {
memmove(result, first, last - first);
return result + (last - first);
}
inline wchar_t* uninitialized_copy(const wchar_t* first, const wchar_t* last,
wchar_t* result) {
memmove(result, first, sizeof(wchar_t) * (last - first));
return result + (last - first);
}
template <class ForwardIterator, class T>
inline void
__uninitialized_fill_aux(ForwardIterator first, ForwardIterator last,
const T& x, __true_type)
{
fill(first, last, x);
}
template <class ForwardIterator, class T>
void
__uninitialized_fill_aux(ForwardIterator first, ForwardIterator last,
const T& x, __false_type)
{
ForwardIterator cur = first;
__STL_TRY {
for ( ; cur != last; ++cur)
construct(&*cur, x);
}
__STL_UNWIND(destroy(first, cur));
}
template <class ForwardIterator, class T, class T1>
inline void __uninitialized_fill(ForwardIterator first, ForwardIterator last,
const T& x, T1*) {
typedef typename __type_traits<T1>::is_POD_type is_POD;
__uninitialized_fill_aux(first, last, x, is_POD());
}
template <class ForwardIterator, class T>
inline void uninitialized_fill(ForwardIterator first, ForwardIterator last,
const T& x) {
__uninitialized_fill(first, last, x, value_type(first));
}
// Valid if copy construction is equivalent to assignment, and if the
// destructor is trivial.
template <class ForwardIterator, class Size, class T>
inline ForwardIterator
__uninitialized_fill_n_aux(ForwardIterator first, Size n,
const T& x, __true_type) {
return fill_n(first, n, x);
}
template <class ForwardIterator, class Size, class T>
ForwardIterator
__uninitialized_fill_n_aux(ForwardIterator first, Size n,
const T& x, __false_type) {
ForwardIterator cur = first;
__STL_TRY {
for ( ; n > 0; --n, ++cur)
construct(&*cur, x);
return cur;
}
__STL_UNWIND(destroy(first, cur));
}
template <class ForwardIterator, class Size, class T, class T1>
inline ForwardIterator __uninitialized_fill_n(ForwardIterator first, Size n,
const T& x, T1*) {
typedef typename __type_traits<T1>::is_POD_type is_POD;
return __uninitialized_fill_n_aux(first, n, x, is_POD());
}
template <class ForwardIterator, class Size, class T>
inline ForwardIterator uninitialized_fill_n(ForwardIterator first, Size n,
const T& x) {
return __uninitialized_fill_n(first, n, x, value_type(first));
}
// Copies [first1, last1) into [result, result + (last1 - first1)), and
// copies [first2, last2) into
// [result, result + (last1 - first1) + (last2 - first2)).
template <class InputIterator1, class InputIterator2, class ForwardIterator>
inline ForwardIterator
__uninitialized_copy_copy(InputIterator1 first1, InputIterator1 last1,
InputIterator2 first2, InputIterator2 last2,
ForwardIterator result) {
ForwardIterator mid = uninitialized_copy(first1, last1, result);
__STL_TRY {
return uninitialized_copy(first2, last2, mid);
}
__STL_UNWIND(destroy(result, mid));
}
// Fills [result, mid) with x, and copies [first, last) into
// [mid, mid + (last - first)).
template <class ForwardIterator, class T, class InputIterator>
inline ForwardIterator
__uninitialized_fill_copy(ForwardIterator result, ForwardIterator mid,
const T& x,
InputIterator first, InputIterator last) {
uninitialized_fill(result, mid, x);
__STL_TRY {
return uninitialized_copy(first, last, mid);
}
__STL_UNWIND(destroy(result, mid));
}
// Copies [first1, last1) into [first2, first2 + (last1 - first1)), and
// fills [first2 + (last1 - first1), last2) with x.
template <class InputIterator, class ForwardIterator, class T>
inline void
__uninitialized_copy_fill(InputIterator first1, InputIterator last1,
ForwardIterator first2, ForwardIterator last2,
const T& x) {
ForwardIterator mid2 = uninitialized_copy(first1, last1, first2);
__STL_TRY {
uninitialized_fill(mid2, last2, x);
}
__STL_UNWIND(destroy(first2, mid2));
}