STL之二级空间配置器及实现

本文深入探讨C++ STL中的二级空间配置器,介绍其作用在于减少小内存块导致的碎片。内容包括二级空间配置器的工作原理、内存分配策略、源码分析、模拟实现以及内存释放过程。通过对不同分配情况的分析,揭示了内存管理的高效机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

之前对于配置器的原理及一级配置器的介绍请看博文
这里写链接内容
下来我们直接介绍二级空间配置器。

二级空间配置器

我们通过之前的学习,已经知道。
如果所要申请的空间大于128字节,则直接交至一级空间配置器处理,如果小于128字节,则使用二级空间配置器。
二级空间配置器对内存的管理减少了小区块造成的内存碎片,它主要通过一个链表管理:
它是用一个16(128/8)个元素的自由链表来管理的,每个位置下挂载着大小为8的倍数个字节(分别为8、16、24、32、48、56、64、72、80、88、96、104、112、120、128字节),每次将所需内存提升到8的倍数。
free_list它的节点结构为一个共用体:

union obj
{
    union obj * free_list_link;
    char client_data[1];
};

二级空间配置器结构如下图所示:
这里写图片描述
如图所示:
在链表中空间没有分配出去之前,该空间中一直存放的是所指向的下一个节点的地址,当该空间分配出去时,让对应的my_free_list指向它的下一个节点,然后将它内部的值改为要放入用户的data值。

如何分配内存

它的内存分配主要分为以下几种情况:
1、对应的free_list不为空
所需空间大小提升为8的倍数后(如需要13bytes空间,我们会给它分配16bytes大小),所对应的free_list不为空时,它会像上图所示那样直接从对应的free_list中拔出,第一位置向后移动指向。
2、对应的free_list为空,其内存池不为空时:
(1)先检验它剩余空间是否够20个节点大小(即所需内存大小(提升后) * 20),若足够则直接从内存池中拿出20个节点大小空间,将其中一个分配给用户使用,另外19个当作自由链表中的区块挂在相应的free_list下,这样下次再有相同大小的内存需求时,可直接从 free-list 中拨出。
(2)如果不够20个节点大小,则看它是否能满足1个节点大小,如果够的话则直接拿出一个分配给用户,然后从剩余的空间中分配尽可能多的节点挂在相应的free_list中。
(3)如果连一个节点内存都不能满足的话,则将内存池中剩余的空间挂在相应的free_list中(找到相应的free_list),然后再给内存池申请内存。
3、内存池为空,申请内存
此时二级空间配置器会使用malloc()从heap上申请内存,(一次所申请的内存大小为2 * 所需节点内存大小(提升后)* 20 + 一段额外空间)。
4、malloc没有成功
在第三种情况下,如果malloc()失败了,说明heap上没有足够空间分配给我们了,这时,二级空间配置器会从比所需节点空间大的free_list中一一搜索,从任意一个比它所需节点空间大的free_list中拔除一个节点来使用。
5、查找失败,调用一级空间配置器
第四种情况下,如果查找失败了,说明比其大的free_list中都没有自由区块了,此时会调动一级空间配置器oom_allocate()。
(一级空间配置器文首给出了博文链接)
内存池的概念请看博文这里写链接内容

部分源码分析

按照上面内存分配的步骤,我们一步步来看:

/*将 __bytes 上调至最邻近的 8 的倍数*/  
static size_t  _S_round_up(size_t __bytes)  
{  
    return (((__bytes)+(size_t)_ALIGN - 1) & ~((size_t)_ALIGN - 1));  
}  

/*返回 __bytes 大小的小额区块位于 free-list 中的编号*/  
static  size_t _S_freelist_index(size_t __bytes) {  
    return (((__bytes)+(size_t)_ALIGN - 1) / (size_t)_ALIGN - 1);  
}  

根据用户所需内存块大小,申请内存:

static void* allocate(size_t __n)   //分配大小为 __n 的区块  
{  
    void* __ret = 0;  

    if (__n > (size_t)_MAX_BYTES) {  
        __ret = malloc_alloc::allocate(__n);    //大于128 bytes 就调用第一级配置器  
    }  
    else { //__n 大小区块对应的位置:free-lists 首地址 + __n 位于free-lists 中的编号  
        _Obj* __STL_VOLATILE* __my_free_list    //这里是二级指针,便于调整 free-lists   
            = _S_free_list + _S_freelist_index(__n);          

#     ifndef _NOTHREADS  
        _Lock __lock_instance;  
#     endif  

        _Obj* __RESTRICT __result = *__my_free_list; //将对应位置的区块拨出(第一个)  
        if (__result == 0)                       //如果 free-lists 中没有对应大小的区块  
            __ret = _S_refill(_S_round_up(__n)); //调用 _S_refill()  
        else {  
            *__my_free_list = __result->_M_free_list_link;//这个结构有点类似链式哈希表结构,这里是指向下一块空闲内存块    
            //二级指针调整 free-lists,拨出去的区块就不属于该链表了  
            __ret = __result;                                 
        }  
    }  

    return __ret;  
};  

如果 free-list 中没有对应大小的区块,转去调用 _S_refill():

template <bool __threads, int __inst>  
void*           //重新填充__n大小的区块进 free-list  
__default_alloc_template<__threads, __inst>::_S_refill(size_t __n)    
{  
    int __nobjs = 20;      //缺省取得 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;  

    /*如果只获得一个新区块,直接划给用户,free-list 仍然无新节点*/  
    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);   
    /*接下来的区块(拨出去了__n大小给用户)填补进 free-list*/  
    for (__i = 1;; __i++) {  
        __current_obj = __next_obj;   
        __next_obj = (_Obj*)((char*)__next_obj + __n);   
        /*将分配的连续大块"分割"成__n bytes大小的区块*/  
        if (__nobjs - 1 == __i) {  //如果新区块填补完毕  
            __current_obj->_M_free_list_link = 0;  //free-list 最后位置指向0  
            break;  
        }  
        else {
  
  //把_M_free
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值