STL源码剖析(一)

STL源码剖析(一)

SGI STL的配置器与众不同,与标准规范不同,其名称是alloc而非allocator,且不带任何参数。因此要采用SGI配置器需要这样写 :
vector<int ,std::alloc> iv;

其实SGI 也定义有了一个标准的空间配置器std::allocator,但是其效率不佳,只是把C++的new 和delete包装起来而已。

STL allocator 将 内存配置释放和内容构造析构这两阶段操作区分开来。内存配置由alloc:allocata()负责, 内存释放操作由alloc::deallocate()负责,对象构造由construct()负责,析构用::destroy()负责。
在这里插入图片描述

构造和析构

在STL中空间配置时候destory()函数会判断要释放的迭代器的指向的对象有没有 trivial destructor(STL中_type_traits<>有一个 has_trivial_destructor函数,很容易实现检测)放,如果有trivial destructor则什么都不做,如果没有即需要执行一些操作,则执行真正的destory函数。destory()有两个版本,第一个版本接受一个指针,准备将该指针所指之物析构掉,第二个版本接受first和last两个迭代器,准备将[first,last]范围内的所有对象析构掉。我们不知道这个范围有多大,万一很大,而每个对象的析构函数都无关痛痒,那么一次次调用这些析构函数,对效率是一种伤害,因此这里首先利用value_type()获得迭代器所指对象的类别,再利用_type_traits判断该型别的析构函数是否无关痛痒,若是(_true_type),则什么也不做就结束,若否(_false_type),这才以循环的方式巡访整个范围,并在循环中每经历一个对象就调用第一个版本的destory()。

空间的配置与释放

对象构造前的空间配置和对象析构后的空间释放,由<stl_alloc.h>负责 SGI对此的设计哲学是
1 向 system heap要求空间
2考虑多线程状态
3考虑内存不足的应变措施
4考虑过多小型区块可能造成的内存碎片问题

SGI设计了双层级配置器,第一级配置器直接用malloc()和free(),第二级配置器视情况采用不同的策略,当配置区块超过128bytes,视为过大,采用第一级配置器,当过小,则采用复杂的memory pool方式
由_USE_MALLOC是否定义决定使用哪种配置器
在这里插入图片描述
在这里插入图片描述

第一级配置器_malloc_alloc_template

第一级配置器中 ,主要是使用了 malloc() 、free()、realloc(),提供的接口为allocate()、deallocate()、reallocate(),在看代码中,有一些比较难懂,现在做记录如下:
*void (set_malloc_handler(void (f)()))()
当我们看到set_malloc_handler有函数调用运算符和参数:(void (f)()),说明这是个函数,以一个void ()()类型的函数指针f做为参数(即f是一个函数指针,指向的函数为空参数列表,无返回值),set_malloc_handler前面有一个,所 以这个函数应该返回一个指针,然后指针本身也有空参数列表(),因此该指针指向函数,该函数的返回值也是void,参数列表也为空

综合起来说,就是我们定义了一个函数set_malloc_handler,它接受一个void ()()类型的参数f,返回类型为void ()()。
在源码中,先设定 void(*_malloc_alloc_template::_malloc_alloc_oom_handler)()=0;
这是当内存不足时,需要执行的函数入口,需要由客户端给定,在源码中设定为0,在重新分配以及分配中,当遇到内存不足,首先判断客户端是否提供了对应的函数入口,有则执行,直到成功,没有提供对应函数入口则直接报错。

第二级配置器__default_alloc_template

第二级配置器多了一些机制,避免太多小额区块造成的内存碎片,小额区块也会带来配置的额外负担,负担所占的比例越大,愈显得浪费。
SGI第二级配置器的做法是:如果区块足够大,超过128bytes时,就用第一级配置器,否则,用内存池管理,此法又称为次层配置,每次配置一大块内存,并维护对应的自由链表,(在这里,同个大小的内存块,由同索引的数组负责管理,也就是每个数组的下标都管理着一个链表,链表上的内存大小相同)下次如果再有相同大小的内存需求,就可以直接从链表中拔出,配置器同时负责小额区块的回收,每个内存块需要边界对齐,这为8的倍数,分别管理,8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128bytes.
free-list的节点结构为 union obj{
union obj*free_list_link;
char client_data[1];
};
这个结构有助于节省内存的开销

上调边界的公式 ((bytes)+_ALIGN-1)&~(__ALIGN-1)
其中 bytes表示需要的内存大小,_ALIGN表示需要对齐的边界 如 8
源码中,数组free_list[n],从n=0到n=_MAX_BYTE/_ALIGN,对应的节点分别管理,8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128bytes.的内存大小,在程序中,当客户端需要申请某一特定的内存大小时,可从链表对应管理内存的节点取出,而这需要求得管理该链表的数组对应的索引,在这 求得索引的公式为:
INDEX=((bytes)+_ALIGN-1)/__ALIGN-1

allocate(size_t n)函数 在大于128bytes时直接调用第一级配置器,在小于128bytes时,根据索引找出管理对应内存块的链表,如果有可用的内存块,则使用,并调整freelist,使得
my_free_list=result->free_list
如图所示在这里插入图片描述
当管理对应内存块大小的节点没有多余的内存,则调用refill(size_t n)函数,refill(size_t n)函数中包含着chunk_alloc(n,nobjs)函数,参数的意义表示,n表示内存块的大小,nobjs表示内存块的数量,在refill()中,默认使用chunk_alloc()申请20个n大小的内存块,当只申请到一块时,直接返回给结果使用,因为没有多余的内存块存进链表,如果大于1块,首先将第0块给结果使用,其余纳入对应的链表中,每块的地址=这一块地址加上内存块的大小,将多余的内存块,按链表的形式串接起来。
chunk_alloc(size_t n,int&nobjs)如果内存池剩余空间大小>n
objs,那么就可以直接返回内存起始量,如果内存剩余空间不能完全满足需求量,但足够供应一个以上,则求出剩余的内存空间除以内存块大小能得出多少块内存块,因为在这nobjs为引用,所以调用它的函数能得到其结果。返回起始地址,同时都要注意下次的起始地址的改变。如果一块都不够,先把内存中剩余的零头配置给在这个freelist数组中适当的内存块中,再配置heap空间,来补充内存池,如果heap空间都不够了,就会去找freelist中足够大的区块,如果有尚未使用的,就挖出一块交出,找不到就调用第一级配置器,第一级配置器其实也是调用malloc()来配置内存,但它有out-of-memory处理机制,或许有机会释放它的内存拿来此处使用,如果可以就成功,否则发出bad_alloc异常。
deallocate()空间释放函数,首先判断大小,如果大于128,则由第一级配置器释放,小于128bytes就找出对应的freelist,将区块收回。

内存基本处理工具

STL定义有五个全局函数,作用域未初始化空间上,这样的功能有助于容器的实现,这五个函数分贝是用于构造额定construct(),用于析构的destroy(),另三个函数分别是uninitialized_copy(),uninitialized_fill(),uninitialized_fill_n(),要使用这些函数应该包含,SGI实际定义于<stl_uninialized>,后面三个函数都能够将内存配置与对象构造行为分离开来,最后一个函数会为指定的范围内的所有元素设定相同的值。这三个函数的实现逻辑基本相似,就是首先取出迭代器的value_type,判断该型别是否为POD类型,POD类型就是标量性别或传统的C struct型别,如果是POD型别就采用最有效率的填写方法,如fill_n()copy(),fill(),同时针对char* wchar_t*提供了特化版本,如果不是POD类型则采用保险的construct()方法。
在这里插入图片描述

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值