一、首先,我们来说说,为什么要使用二级容器空间配置器,了解过容器一级空间配置器的同学都应该知道容器一级空间配置器申请,与释放空间是通过malloc和free来实现的,在不断申请和释放小片空间后,如果要在申请表一块儿较大的空间时,就会产生内存碎片,如下图所示:
二级空间就是来管理内存,提高内存的使用效率的。
二、然后,我们来看看空间容器适配器的实现机制,如下图所示:
三、接下来,我们来看看容器二级空间配置器的结构:
如上图所示:
1、容器空间二级配置器是由16个自由链表构成,由一个有16个元素的指针数组管理,数组的每个元素对应存储对应规格大小的第一个内存块儿的地址,即指向首个内存块儿(上图中为了方便起见,数据里写的是内存块儿的大小,和一个一定大小的内存池。
2、由上图也可见free_list所管理的内存区块最小为8bytes,最大为128bytes,且是以8bytes递增。因为涉及到内存对齐问题,所以这里是以8的倍数递增的。
四、接下来,咱们就来开始看源码部分,如果大家感觉源码比较晦涩难懂的话,在第五部分,会有一个图来详细的说明,函数之间的调用过程;
首先向大家介绍free_list的几个常量;
#if defined(__SUNPRO_CC) || defined(__GNUC__)
// breaks if we make these template class members:
enum {_ALIGN = 8};//小型区块的上调边界
enum {_MAX_BYTES = 128};//free_list的区块上限(128bytes)
enum {_NFREELISTS = 16}; //free_list数组中自由链表个数
#endif
将要开辟的内存大小调至8的倍数,与区块儿大小相匹配;
_S_round_up(size_t __bytes)
{ return (((__bytes) + (size_t) _ALIGN-1) & ~((size_t) _ALIGN - 1)); }
//将要开辟的区块大小上调至8的倍数;
定义一个共用体作为自由链表的节点;
union _Obj {
union _Obj* _M_free_list_link;
//将节点链接起来的指针,相当于next指针;
char _M_client_data[1];
//在次不做介绍,后面用不到;
/* The client sees this.*/
};
通过所需区块儿大小推出数组的下标;
static size_t _S_freelist_index(size_t __bytes) {
return (((__bytes) + (size_t)_ALIGN-1)/(size_t)_ALIGN - 1);
//用所需内存大小推出所对应free_list的下标
}
声明填充函数以及内存池的几个参数及指针;
// Returns an object of size __n, and optionally adds to size __n free list.
static void* _S_refill(size_t __n);//声明向free_list中填充内存块儿函数
// Allocates a chunk for nobjs of size size. nobjs may be reduced
// if it is inconvenient to allocate the requested number.
static char* _S_chunk_alloc(size_t __size, int& __nobjs);//声明从内存池获取区块儿函数
// Chunk allocation state.
static char* _S_start_free;
//指向在内存池中未分配给free_list的内存的首地址
static char* _S_end_free;
//指向在内存池中未分配给free_list的内存的末地址
static size_t _S_heap_size;
//内存池的总大小
free_list[ ]的定义
# if defined(__SUNPRO_CC) || defined(__GNUC__) || defined(__HP_aCC)
static _Obj* __STL_VOLATILE _S_free_list[];
static _Obj* __STL_VOLATILE _S_free_list[_NFREELISTS];
//定义一个元素大小为16的指针数组(定义free_list[]);
二级容器空间配置器的内存开辟
static void* allocate(size_t __n)//开辟空间
{
void* __ret = 0;
if (__n > (size_t) _MAX_BYTES) {
__ret = malloc_alloc::allocate(__n);
//如果要开辟的内存大小大于128bytes,则由一级容器配置器开辟空间
}
else {
_Obj* __STL_VOLATILE* __my_free_list
= _S_free_list + _S_freelist_index(__n);
//my_free_list 为指向my_free_list的二级指针,通过free_list的首地址加上下标,为所需内存大小free_list的下标
# ifndef _NOTHREADS
_Lock __lock_instance;
# endif
_Obj* __RESTRICT __result = *__my_free_list;
//将_my_free_list指向的free_list中存的指向obj的地址赋值给__result;
if (__result == 0)
__ret = _S_refill(_S_round_up(__n));
//如果__result == 0,说明free中为空,没有区块内存,则调用_s_refill函数向free_list填充区块内存
else {
*__my_free_list = __result -> _M_free_list_link;
//将free_list指向下一个区块儿内存节点
__ret = __result;
//将free_list中指向obj的地址赋值给_ret;
}
}
return __ret;
//返回_ret;
};
二级容器空间配置器内存的释放,相当于链表的头插
static void deallocate(void* __p, size_t __n)//释放空间
{
if (__n > (size_t) _MAX_BYTES)
malloc_alloc::deallocate(__p, __n);
//如果释放空间大于128bytes,用一级容器适配器释放
else {
_Obj* __STL_VOLATILE* __my_free_list
= _S_free_list + _S_freelist_index(__n);
//定义一个二级指针,并且指向对应规格大小的数组元素;
_Obj* __q = (_Obj*)__p;
//将p强转为联合体类型并赋给q;
# ifndef _NOTHREADS
_Lock __lock_instance;
# endif /* _NOTHREADS */
__q -> _M_free_list_link = *__my_free_list;
//让q指向下一个节点;
*__my_free_list = __q;
//让头指针指向插进来的区块;
}
//相当于链表的头插;
}
下面是二级容器空间配置器的重头戏,从内存池获取内存块儿;
__default_alloc_template<__threads, __inst>::_S_chunk_alloc(size_t __size,
int& __nobjs)//从内存池中获取区块儿内存,size为free_list下的内存区块的大小,_nobjs申请内存区块儿的个数
{
char* __result;
size_t __total_bytes = __size * __nobjs;
//total_bytes 为从内存池获取内存的大小
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);
//如果剩余内存>=所需内存,则将内存的首地址给_result,再将内存池的首地址移动
} else if (__bytes_left >= __size) {
__nobjs = (int)(__bytes_left/__size);
__total_bytes = __size * __nobjs;
__result = _S_start_free;
_S_start_free += __total_bytes;
//因为当要向内存区块取内存时,说明free_list已经为空,一次尽量多的获取内存块儿
//这里是当内存池剩余内存大小大于一个区块,而又满足不了默认区块获取内存大小的时候,就缩小_nobjs大小
//然后再重复上一个if的操作
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);
//确定大小为__byte_left大小的区块对应的下标
((_Obj*)_S_start_free) -> _M_free_list_link = *__my_free_list;
//将_s_start_free插入第一个区块之前
*__my_free_list = (_Obj*)_S_start_free;//
}//在内存池向操作系统申请内存的时候先将空间分配给free_list(从此可以推断出内存池的大小应该也是8的倍数)
_S_start_free = (char*)malloc(__bytes_to_get);//向操作系统申请内存给内存池
if (0 == _S_start_free)
{ // 如果_S_start_free为0,则说明malloc失败
size_t __i;
_Obj* __STL_VOLATILE* __my_free_list;
_Obj* __p;
for (__i = __size;//从这就要开始在free_list下借用后面的区块儿内存
__i <= (size_t) _MAX_BYTES;//
__i += (size_t) _ALIGN)
{
__my_free_list = _S_free_list + _S_freelist_index(__i);
__p = *__my_free_list;//p就是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));//这里就是内存池里的内存不够用,然后就将比当前内存块儿大的进行回收,然后再让获取
}
}
_S_end_free = 0; // In case of exception.
_S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get);
}
_S_heap_size += __bytes_to_get;
_S_end_free = _S_start_free + __bytes_to_get;
return(_S_chunk_alloc(__size, __nobjs));
}
}
最后,就是向链表中填充内存块儿;
void*
__default_alloc_template<__threads, __inst>::_S_refill(size_t __n)//向free_list填充区块儿内存
{
int __nobjs = 20;//填充默认值为20
char* __chunk = _S_chunk_alloc(__n, __nobjs);
//从内存池中获取内存块儿
_Obj* __STL_VOLATILE* __my_free_list;
//指向free_list的二级指针
_Obj* __result;
_Obj* __current_obj;
_Obj* __next_obj;
//定义三个指向_obj的指针
int __i;
if (1 == __nobjs) return(__chunk);//如果只只获取了一个内存块儿,就直接交给用户使用;
__my_free_list = _S_free_list + _S_freelist_index(__n);
//如果不唯一计算出内存块儿在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 -> _M_free_list_link = 0;
break;
} else {
__current_obj -> _M_free_list_link = __next_obj;
}
}
//将剩下的内存块儿插入链表中;
return(__result);
}
下面为了让大家对二级容器空间配置器有更深的了解,贴上一张函数 调用过程的图片
以上就是容器二级空间配置的源码剖析的所有内容,希望大家看了后,能有收获。若有误,欢迎大家指导。