stl allocator
2019年8月24日 星期六, 发表于 深圳
如果你对本文有任何的建议或者疑问, 可以在 这里给我提 Issues, 谢谢! :)
stl allocator 是stl中负责空间管理(空间申请/释放)的部分;
1. alloctor和容器
总体来看,stl allocator是一个模版类,通过组合方式和stl中的容器进行组合,例如
std::vector<int> first; //构造一个int的vector
//实际是是执行下面的构造函数
template < class T, class Alloc = allocator<T> >
class vector;
stl通过模板参数将空间分配和销毁的任务委托给alloctor
alloctor主要负责四件事:
- 分配空间;
- 构造对象实例;
- 析构对象实例;
- 释放空间;
参考这个文章https://zhuanlan.zhihu.com/p/34725232里面的图片
在SGI-STL中,分为两级配置器,第一级配置器主要是对malloc和free的封装,以及处理内存不足时的set new handler处理,第二级配置器实现内存池,避免了小型对象频繁分配导致内存碎片;
1.1 SGI-STL 第二级适配器
以下内容来自于《STL 源码剖析》
SGI第二级配置器做法是,如果区块够大,超过128bytes时,将移交第一级配置器处理。当小于128bytes,则以内存池管理,此法又称为次级配置:每次配置一大块内存,并维护对应之自由链表free-lists。下次若有相同大小的内存需求,直接从free-lists中拨出。如果客端释放小额块区,就由配置器回收到free-lists中
为了方便管理,SGI第二级配置器主动将任何小额块区的内存需求量上调至8的倍数(如,客端要求30bytes,则自动调整为32bytes),并维护16个free-lists,各自分别管理大小为8、16、24、32、……、120、128bytes的小额块区。
这一块的数据结构和代码设计的相当精巧,可以参考下面的链接 https://blog.csdn.net/u013074465/article/details/44560541
在stl_alloc.h这个头文件中,实现了alloc函数,alloc的流程大致为:
- 如果分配空间大于128字节,调用以及分配器(malloc和free);
- 否则进行如下判断:
2.1) 根据申请空间找到对应的空闲链表;
2.2) 如果对应空闲链表为空,则调用refill先分配free list,然后再分配;
refill的流程发生在对应的链表为非空闲状态的情况下,refill通过调用chunk_alloc为free list填充空间,默认情况下,新区块获取20个新节点;
refill将chunk_alloc获得到的节点通过指针串联起来,相对逻辑比较简单;
其中最复杂的部分应该是chunk_alloc。
参考下面的代码:https://github.com/steveLauwh/SGI-STL/blob/master/SGI-STL%20V3.3/stl_alloc.h
chunk_alloc()函数以 end_free - start_free 来判断内存池的水量。
- 如果水量充足,就直接调用 20 个区块返回给 free list;
- 如果水量不足以提供 20 个区块,但还最后供应一个以上的区块,就拔出这不足 20 个区块的空间出去这时候其 pass by reference的nobjs参数将被修改为实际能够供应的区块数。
- 如果内存池连一个区块空间都无法供应,对客端显然无法交待,此时便需利用 malloc() 从head中配置内存,为内存池注入源头活水以应付需求。新水量的大小为需求量的两倍,再加上一个随着配置次数增加而愈来愈大的附加量。
3.1) 如果不能Malloc出来,则从更大的区块尝试借一些区块过来使用;
// 从内存池中取空间
template <bool __threads, int __inst>
char*
__default_alloc_template<__threads, __inst>::_S_chunk_alloc(size_t __size,
int& __nobjs)
{
char* __result;
size_t __total_bytes = __size * __nobjs; // 需要申请空间的大小
size_t __bytes_left = _S_end_free - _S_start_free; // 计算内存池剩余空间
if (__bytes_left >= __total_bytes) { // 内存池剩余空间完全满足申请
__result = _S_start_free;
_S_start_free += __total_bytes;
return(__result);
} else if (__bytes_left >= __size) { // 内存池剩余空间不能满足申请,提供一个以上的区块
__nobjs = (int)(__bytes_left/__size);
__total_bytes = __size * __nobjs;
__result = _S_start_free;
_S_start_free += __total_bytes;
return(__result);
} else { // 内存池剩余空间连一个区块的大小都无法提供
size_t __bytes_to_get =
2 * __total_bytes + _S_round_up(_S_heap_size >> 4);
// Try to make use of the left-over piece.
// 内存池的剩余空间分给合适的空闲链表
if (__bytes_left > 0) {
_Obj* __STL_VOLATILE* __my_free_list =
_S_free_list + _S_freelist_index(__bytes_left);
((_Obj*)_S_start_free) -> _M_free_list_link = *__my_free_list;
*__my_free_list = (_Obj*)_S_start_free;
}
_S_start_free = (char*)malloc(__bytes_to_get); // 配置 heap 空间,用来补充内存池
if (0 == _S_start_free) { // heap 空间不足,malloc() 失败
size_t __i;
_Obj* __STL_VOLATILE* __my_free_list;
_Obj* __p;
// Try to make do with what we have. That can't
// hurt. We do not try smaller requests, since that tends
// to result in disaster on multi-process machines.
for (__i = __size;
__i <= (size_t) _MAX_BYTES;
__i += (size_t) _ALIGN) {
__my_free_list = _S_free_list + _S_freelist_index(__i);
__p = *__my_free_list;
if (0 != __p) {
*__my_free_list = __p -> _M_free_list_link;
_S_start_free = (char*)__p;
_S_end_free = _S_start_free + __i;
return(_S_chunk_alloc(__size, __nobjs));
// Any leftover piece will eventually make it to the
// right free list.
}
}
_S_end_free = 0; // In case of exception.
_S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get); // 调用第一级配置器
// This should either throw an
// exception or remedy the situation. Thus we assume it
// succeeded.
}
_S_heap_size += __bytes_to_get;
_S_end_free = _S_start_free + __bytes_to_get;
return(_S_chunk_alloc(__size, __nobjs)); // 递归调用自己
}
}
1.2 STL Allocator代码跟踪
可以参考一下这个博客的跟踪流程:https://blog.csdn.net/My_heart_/article/details/52716830
也可以参考我自己抽取出来的一段代码,一步步调试: 链接为: https://github.com/illx10000/relax/blob/master/algorithm/stl/stl_allocator_debug.cpp