在C++中所有STL容器的空间分配其实都是使用的std::allocator,它是可以感知类型的空间分配器,并将空间的申请与对象的构建分离开来。
stl的空间配置器运用了链表+内存池和分级控制的技术。
对象池简易实现
struct __allocator
{
_Alloc __underlying_alloc;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef _Tp* pointer;
typedef const _Tp* const_pointer;
typedef _Tp& reference;
typedef const _Tp& const_reference;
typedef _Tp value_type;
template <class _Tp1> struct rebind {
typedef __allocator<_Tp1, _Alloc> other;
};
//对象的创建
void construct(pointer __p, const _Tp& __val)
{
new(__p) _Tp(__val); //为new定位符
}
construct(对象的创建),定位new表达式,在指定空间上申请对象
对象的销毁有两个版本:
1:接收一个指针,将其释放掉
2:接收一个迭代器范围,将其范围内的对象析构掉
1:void destroy(pointer __p) { __p->~_Tp(); }
2:typedef _Tp value_type;
//判断该对象类型的析构函数是否无关紧要,如果不重要
//就不需要循环释放一个范围,万一范围很大,得不偿失
以value_type()萃取出迭代器的value type,再利用__type_traits判断该类型是否POD类型
__uninitialized_fill_n_aux(_ForwardIter __first, _Size __n,
const _Tp& __x, __true_type)
{
return fill_n(__first, __n, __x);
//判断是否为POD类型,实现---------------------------------
inline _ForwardIter
__uninitialized_fill_n(_ForwardIter __first, _Size __n, const _Tp& __x, _Tp1*)
{
is_POD_type _Is_POD;//POD类型
return __uninitialized_fill_n_aux(__first, __n, __x, _Is_POD());
}
}
走向两个分支,一个递归回去,一个直接释放
__uninitialized_fill_n_aux(_ForwardIter __first, _Size __n,
const _Tp& __x, __true_type)
{
return fill_n(__first, __n, __x);
}
__uninitialized_fill_n_aux(_ForwardIter __first, _Size __n,
const _Tp& __x, __false_type)
{
_ForwardIter __cur = __first;
__STL_TRY {
for ( ; __n > 0; --__n, ++__cur)
_Construct(&*__cur, __x);
return __cur;
}
__STL_UNWIND(_Destroy(__first, __cur));//释放函数
}
空间的申请分为一级和二级
_Tp* allocate(size_type __n, const void* = 0)
{
return __n != 0 ? static_cast<_Tp*>(_Alloc::allocate(__n * sizeof(_Tp))) : 0;
//类型转换可以忽略不看
__n != 0 ? (_Alloc::allocate(__n * sizeof(_Tp))) : 0;
//allocate是申请函数分为2种
}
当大于等于128kb时,和小于128kb时
1:考虑小块空间过多造成的内存碎片
2:尽可能地减少对malloc的调用,因为会频繁陷入用户态和内核态
3:是一个以空间换时间的策略,跟缓冲区、struct内存对齐一样
4:考虑内存不足的应对策略
一级空间配置器
# ifdef __USE_MALLOC
typedef malloc_alloc alloc;
typedef malloc_alloc single_client_alloc;
# else
编译时,g++ -D,在编译时添加参数的__USE_MALLOC
class __malloc_alloc_template
{
private:
static void* _S_oom_malloc(size_t);
static void* _S_oom_realloc(void*, size_t);
#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
//此宏可能是GNU_C的标准下
//可以定义0长度数组,可变参数宏
static void (* __malloc_alloc_oom_handler)();//一个函数指针
#endif
public:
static void* allocate(size_t __n)
{
void* __result = malloc(__n);
if (0 == __result) __result = _S_oom_malloc(__n);
return __result;
}
static void deallocate(void* __p, size_t /* __n */)
{
free(__p);
}
};
主要函数就是 **static void* allocate(size_t __n)
**此函数为内存申请
static void deallocate(void* __p, size_t );//内存释放
allocate()内部实现
两级空间配置器,第一级空间配置器使用类模板malloc_alloc_template ,其底层使用的malloc/free进行空间的申请与释放,二级空间配置器使用类模板,default_alloc_template,其底层申请空间大小有分为两个分支进行。
第一分支是当申请的空间大于128字节的时候,还是走 __malloc_alloc_template ,当申请的空间小于128字节的使用,使用内存池+16个自由链表的结构进行。
二级空间配置器
static void* allocate(size_t __n)
{
void* __ret = 0;
//_MAX_BYTES为128定义的宏
//当申请内存空间大于128字节时
if (__n > (size_t) _MAX_BYTES)
{
__ret = malloc_alloc::allocate(__n);//正常申请
}
else
{
//_obj是一个二级指针,调用了S_freelist_index(__n);
_Obj* __STL_VOLATILE* __my_free_list= _S_free_list + _S_freelist_index(__n);
//偏移3的位置,如下图
//函数实现是
//-----------------------------------------------------------------------------
static size_t _S_freelist_index(size_t __bytes)
{
//__bytes是申请空间的大小,定32 ALIGN=8 39/8-1=4-1=3;
return (((__bytes) + (size_t)_ALIGN-1)/(size_t)_ALIGN - 1);
}
//-----------------------------------------------------------------------------
//S_free_list是一个类的静态成员都初始化为0了
template <bool __threads, int __inst>
_Obj* __STL_VOLATILE
::_S_free_list[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
//result指向了32这个位置
_Obj* __RESTRICT __result = *__my_free_list;//
if (__result == 0)//因为my_free_list都被初始化为0了,所以怎么偏移,都是满足如下条件的
{
__ret = _S_refill(_S_round_up(__n));//_S_round_up (32 + 7) & ~ 7
static size_t _S_round_up(size_t __bytes)
{ return (((__bytes) + (size_t) _ALIGN-1) & ~((size_t) _ALIGN - 1)); }
(32 + 7) & ~ 7
0010 0111
& 1111 1000
0010 0000//功能:向上取整,得到8的整数倍,25-32,最后的结果都是32传出
}
//_S_refill过于复杂,放到下面
else
{
*__my_free_list = __result -> _M_free_list_link;
__ret = __result;
}
}
return __ret;
};
_S_refill函数实现
功能:将申请的内存进行切割,切割成20块,并且将他们链起来存储在链表中
void* _S_refill(size_t __n)
{
int __nobjs = 20;
char* __chunk = _S_chunk_alloc(__n, __nobjs);
//_S_chunk_alloc函数将数据组块,见如下
_Obj* __STL_VOLATILE* __my_free_list;
_Obj* __result;
_Obj* __current_obj;
_Obj* __next_obj;
int __i;
if (1 == __nobjs) return(__chunk);
__my_free_list = _S_free_list + _S_freelist_index(__n);
__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 -> _M_free_list_link = 0;
break;
} else {
__current_obj -> _M_free_list_link = __next_obj;
}
}
return(__result);
}
_S_chunk_alloc函数
功能:将数据组成块状
数据
union _Obj
{
union _Obj* _M_free_list_link;
char _M_client_data[1]; /* The client sees this. */
};
char* _S_start_free = 0;
char* _S_end_free = 0;
size_t _S_heap_size = 0;
char* _S_chunk_alloc(size_t __size, int& __nobjs)//32 20
{
char* __result;
size_t __total_bytes = __size * __nobjs = 32 * 20 = 640;
size_t __bytes_left = _S_end_free - _S_start_free = 0;
else
{
size_t __bytes_to_get = 2 * __total_bytes + _S_round_up(_S_heap_size >> 4)
= 2 * 640 + 0 = 1280;
_S_start_free = (char*)malloc(__bytes_to_get) = (char *)malloc(1280);
_S_heap_size += __bytes_to_get = 1280;
_S_end_free = _S_start_free + __bytes_to_get;
//将内存块的前后都给涵盖住,begin(),end()
return(_S_chunk_alloc(__size, __nobjs));//32,20,再次调用
//递归调用
char* __result;
size_t __total_bytes = __size * __nobjs = 32 * 20 = 640;
size_t __bytes_left = _S_end_free - _S_start_free = 1280;
if (__bytes_left >= __total_bytes)
{
__result = _S_start_free;
_S_start_free += __total_bytes;//从640字节开始
return(__result);
}
}
}
空间配置器的释放
如果大于128个字节的申请时,调用malloc_alloc::deallocate(__p, __n);一级配置器释放
static void deallocate(void* __p, size_t __n)//32
{
if (__n > (size_t) _MAX_BYTES)//128
malloc_alloc::deallocate(__p, __n);//free
//----------------函数实现------------------------------
static void deallocate(void* __p, size_t)
{
free(__p);
}
else
//二级空间配置器的释放
{
_Obj** __my_free_list = _S_free_list + _S_freelist_index(__n);
//_S_free_list[3]
_Obj* __q = (_Obj*)__p;
//将其指针指向待释放的空间,并且链回到自由链表上
__q -> _M_free_list_link = *__my_free_list;
*__my_free_list = __q;
}
}
```cpp
// 以《STL源码剖析》这本书的例子进行研究,先申请32字节空间,
然后申请64字节空间。
//接着申请96字 节空间,最后申请72字节
//(假设此时内存池耗尽、堆空间没有大于72字节的连续空间) 。
_free_list = _S_free_list + _S_freelist_index(__n);
//_S_free_list[3]
_Obj* __q = (_Obj*)__p;
//将其指针指向待释放的空间,并且链回到自由链表上
__q -> _M_free_list_link = *__my_free_list;
*__my_free_list = __q;
}
}
以《STL源码剖析》这本书的例子进行研究,先申请32字节空间,然后申请64字节空间,接着申请96字 节空间,最后申请72字节(假设此时内存池耗尽、堆空间没有大于72字节的连续空间) 。
二级空间配置器,直接将用完后的空间链回到相应的链表下面,使用头插法进行连接。