STL空间配置器

本文探讨了STL中空间配置器的设计理念及其作用,包括解决内存碎片问题、提高内存分配效率。详细介绍了两级空间配置器的工作原理,一级配置器通过封装malloc、free等函数实现内存管理,二级配置器通过维护自由链表来高效地复用小块内存。

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

一、STL为什么需要空间配置器?(内存有关)
1、解决内存碎片(外碎片)
外碎片:小块内存频繁分配导致小块空间不连续,分配不出大块的空间。
内碎片:比如需要3个字节,向上对齐取整new了8个字节,那么这5个未使用的为内碎片。
解决外碎片会引入内碎片
比如list、map、set、hashtable等push(new)或pop(delete)操作都有小空间。
2、提高效率:频繁分配空间,每一次都有系统调用

二、STL空间配置器框架:
一级空间配置器和二级空间配置器

这里写图片描述

1、一级空间空间配置器是对malloc、free和realloc的封装
(1)allocate调用malloc
(2)deallocate调用free
(3)模拟C++的set_new_hander()
set_new_hander():分配内存失败处理函数句柄。在内存空间分配失败的情况下,如果设置了该句柄,则不会直接抛异常,而是执行这个处理函数,因为__malloc_alloc_oom_handler是全局的,可在外释放一些空间,那么有可能下次循环就会分配成功。不断的尝试分配,直至分配成功;否则(未设置该句柄),抛出异常。
STL空间配置器使用的是malloc分配空间,所以设计的是set_malloc_hander()。

2、二级空间配置器
(1)维护16个自由串列(free_list)
(2)需求大于128,直接调用一级空间配置器
(3)需求不大于128,检查对应的free_list,如果free_list有可用的直接用,否则向上取整8、16、24…(允许小小的浪费)。然后调用refill(),为free_list重新填充空间。
设计:
free_list结构:
这里写图片描述

chunk_alloc():内存池给free_list提供内存块,由chunk_alloc()完成。
(1)当内存池剩余字节足够为需求分配,直接分配,并更新内存池水位线;
(2)如果内存池已经不够分配总需求,但是至少足够分给一个对象,那么能满足几个对象的大小就给几个;
(3)如果内存池已经不够一个对象的大小了,先把剩余的插入到自由链表合适的位置;
当内存池为空,申请两倍+的空间,一部分给free_list,一部分存在内存池。
如果大量申请导致整个系统的堆空间都不够了,malloc失败。那么chunk_alloc在free_list寻找可用空间块,若找到则交出,否则调用一级空间配置器,因为一级空间配置器也许有set_malloc_handler机制,有可能释放其他空间、分配成功,否则抛出异常。

char* chunkAlloc(size_t size,int& nobjs)
{
    char* result ;
    size_t bytesNeed=size*nobjs;//需求字节数
    size_t bytesLeft=_endFree-_startFree;//内存池剩余字节数
    if(bytesLeft>=bytesNeed)//剩余足够满足总需求
    {
        result=_startFree;
        _startFree+=bytesNeed;//更新内存池水位线
        return result;
    }
    else if(bytesLeft>=size)//剩余不够满足总需求,但可满足至少一个对象的大小
    {
        result =_startFree;
        nobjs=bytesLeft/size;//先分配bytesLeft/size个对象
        _startFree+=nobjs*size;//更新内存池水位线
    }
    else//内存池剩余不足一个对象的需求
    {
        if(bytesLeft>0)//剩余一些零头,先给free_list
        {
            size_t index=FREELIST_INDEX(bytesLeft);
            ((Obj*)_startFree)->_freeListLink=_freeList[index];
            _freeList[index]=(Obj*)_startFree;
            _startFree=NULL;
        }
        //从系统堆分配两倍+
        size_t bytesToGet=2*bytesNeed+ROUND_UP(_heapSize>>4);
        _startFree=(char*)malloc(bytesToGet);

        //系统堆空间不足,分配失败
        if(NULL==_startFree)
        {
            //先去自由链表找
            for(int i=size;i<_MAX_BYTES;i+=_ALIGN)
            {
                Obj* head=_freeList[FREELIST_INDEX(size)];
                if(head)
                {
                    _startFree=(char*)head;
                    head=head->_freeListLink;
                    _endFree=_startFree+i;
                    return chunkAlloc(size,nobjs);
                }
            }
            //自由链表无可用块,调一级空间配置器
            _startFree=(char*)MallocAlloc::Allocate(bytesToGet);
        }
    }
}
static void* Allocate(size_t n)
{
    //大于_MAX_BYTES_,调一级空间配置器的Allocate
    if(n>(size_t)_MAX_BYTES_)
    {
        return(MallocAlloc::Allocate(n));
    }
    //否则在自由链表中找适当的
    size_t index=FREELIST_INDEX(n);
    Obj* result=_freeList[index];
    if(NULL==result)
    {
        //自由链表无可用块,调refill()填充
        return refill(ROUND_UP(n));
    }
    //调整自由链表
    _freeList[index]=result->_freeListLink;
    return (result);
}
void Dellocate(void* p,size_t n)
{
    //若n大于128,直接还给一级配置器
    if(n>(size_t)_MAX_BYTES_)
    {
        MallocAlloc::Dellocate(p,n);
    }
    //否则挂回到二级空间配置器自由链表的适当位置
    else
    {
        size_t index=FREELIST_INDEX(n);
        p->_freeListLink=_freeList[index];
        _freeList[index]=p;
    }
}

缺点:
1、引入内碎片,浪费空间
2、二级配置器分配的空间未还给操作系统,释放空间是挂在自由链表上(进程结束才释放)
事实上是它无法还给操作系统,因为内存池从系统堆得到的连续空间被切分成了块挂在自由链表上,而每一个块的使用和释放都不是统一的。假如链表的某一位置那一串想释放,但其中有个别还在使用。所以它还是释放不了。
3、如果一直分配,复用功能达不到
二级空间配置器本质上是想复用内存,自由链表从内存池得到一些内存,client用完后挂回去,下次有需要再从链表里拿。那如果一直分配而不释放,内存空间得不到复用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值