STL空间配置器

STL 空间配置器

一、常规对象配置

​ 我们使用C/C++是对于动态内存对象可以有两种方法申请:1、调用 malloc() + free() 函数;2、调用 new + delete 运算符。

​ 不得不提一下这两种方法的区别,虽然看起来都是向堆区(C++虽然叫自由存储区,但的确是通过堆区实现的)申请内存,但是new和delete在C++中支持C++的高级特性,如下表。比如new、delete支持重载,而且对于初始内存两者的处理完全不同。

特征new/deletemalloc/free
内存分配返回值完整类型指针void*
分配内存大小编译器计算显示指定字节数
已分配内存的扩充nullrealloc
构造函数和析构函数自动调用null
函数重载允许不允许
分配内存不足(下面会提到)客端需要指定处理函数(c++ new handler)用户无法插足

​ 打个比方:malloc() 申请的内存是一块荒地,上面可能荒芜一片或者布满杂草(实际上确实是一个 void* 的返回值);而 new 产生的是一个经过耕耘,精心打理过得沃土(经过了构造)。区别就是 new 在申请内存后,还进行了构造函数的调用,同时 delete 时还会调用析构函数。那么我们引出两个概念:construct()和destroy() 。假设这两个函数是对空间的初始化和去初始化,我们用下列伪代码来模拟 new 和 delete 。

/*	这个new就是模拟 operator new 的行为	
	对象生命期: 内存申请 --> 对象构造 --> 对象析构 --> 内存释放
*/
//模拟new
void* operator new(size_t n)
{
	void * ret = malloc(n);		//申请内存
    construct(ret);			    //对象构造
    return ret;					
}

//模拟delete
void operator delete(void * ptr)
{
    destory(ptr);			   //对象析构
	free(ptr);                  //释放内存
}


好了,到现在我们知道了构造对象的整个流程,截下来就让我们看一下STL空间配置的策略。

二、STL 的双层空间配置器

​ STL设计中考虑到的一个主要问题就是:

  1. ​ 向 heap 申请内存;
  2. ​ 内存不足时的应变策略;
  3. ​ 内存申请产生的“内碎片”;

​ 我们跟着这三个问题来看看STL是如何解决的。

​ 第一个问题和第三个问题殊途同归,基本上都被STL的双层配置器解决了。第一级配置器直接使用 malloc() 和 free() ;第二级配置器则视情况采用不同的策略:当配置区块超过128 bytes 时,将调用第一级配置器;当配置器区块小于128 bytes 时为了降低额外负担,采取一个复杂的 memory pool 整理方式,而不是使用第一级配置器。下面代码概述第一级空间配置器和第二级空间配置器的职能

/*STL 第一级配置器:
1、allocate() 直接使用 malloc() , deallocate() 直接使用 free()
2、模拟c++的 set_new_handler()以处理内存不足的情况
*/
template<int inst>
class __malloc_alloc_template{...};

/*STL 第二级配置器:
1、memory pool中的16个自由链表,负责16中不同大小的小型区块的次配置能力;如若内存不足转调第一级配置器(这种情况一般是需要补充内存池容量了)
2、如果区块要求大于128 bytes,就转调第一级配置器
*/
template<bool threads, int inst>	//第一个参数跟STL的多线程有关,但是有关STL多线程过于庞大以后赘述,第二个参数没用
class __default_alloc_template{...};

​ 第一级不用多说了,纯纯的调用malloc、free,重点是第二级空间配置器。

三、第二级空间配置器 __default_alloc_template

​ 第二级空间配置器多了一些机制,避免太多小额区块造成的内存碎片。不仅如此,申请小区块带来的额外负担也要大。因为 malloc 和 free 需要陷入内核,所以减少 malloc 调用次数非常重要。而简单的策略就是使用内存池,申请一大块内存,然后再将内存分配成小块供用户申请。

​ 第二级配置器的做法是,区块大于128 bytes 移交给第一级配置器。当区块小于128 bytes 则由内存池管理:每次配置一大块内存,并维护对应的自由链表。下次若再有相同大小的内存需求,就直接从free-lists中拨出,如果客端需要归还小额区块,就由配置器回收到free-lists中。free-lists节点如下:

/*free-lists 中节点都是 8 的倍数 字节*/
union obj{
	union obj * free_list_link;
	char client_data[1];
}

​ 一物二用,不会为了维护链表所必须的指针而造成内存的另一种浪费。

​ 空间配置函数allocate() 是一个重要标准接口。此函数首先判断区块大小,根据之前所说的策略进行调整,如果 pool 中内存不足,调用refill(),源码如下:

static void * allocate(size_t n)
{
	obj* volatile * my_free_list;
 	obj* result;
    //大于128
    if(n > (size_t) __MAX_BYTES){
		return (malloc_alloc::allocate(n));
    }
    
    //寻找16个free中合适的
    my_free_list = free_list + FREELIST_INDEX(n);
    result = *my_free_list;
    if( result == 0){//没有可用的 free-lists,调用refill
        void* r = refill(ROUND_UP(n));
        return r;
	}
    
    //调整free list
    *my_free_list = result->free_list_link;
    return (result);
}

四、总结

​ STL高效的内存利用率离不开双层结构的配置器:

  1. 大于 128 bytes,一级空间配置器
  2. 小于 128 bytes,二级空间配置器 + 高效的 memory pool 机制
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

绿色健康老清新

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值