c++-第25节-STL之空间配置器-创新互联
目录
创新互联主要从事网站制作、网站设计、网页设计、企业做网站、公司建网站等业务。立足成都服务通化,十载网站建设经验,价格优惠、服务专业,欢迎来电咨询建站服务:135182197921.什么是空间配置器
2.为什么需要空间配置器
3.SGI-STL空间配置器实现原理
4.STL空间配置器的使用
1.什么是空间配置器
空间配置器,顾名思义就是 为各个容器高效的管理空间(空间的申请与回收),在默默地工作。虽然在常规使用STL时,可能用不到它,但站在学习研究的角度,学习它的实现原理对我们有很大的帮助。
2.为什么需要空间配置器
前面在模拟实现vector、list、map、unordered_map等容器时,所有需要空间的地方都是通过new申请的,虽然代码可以正常运行,但是有以下不足之处: 空间申请与释放需要用户自己管理,容易造成内存泄漏 频繁向系统申请小块内存块,容易造成内存碎片 频繁向系统申请小块内存,影响程序运行效率 直接使用malloc与new进行申请,每块空间前有额外空间浪费(因为每块空间都要开额外的内存空间记录这块空间的大小) 申请空间失败怎么应对 代码结构比较混乱,代码复用率不高 未考虑线程安全问题因此需要设计一块高效的内存管理机制。
3.SGI-STL空间配置器实现原理
在windows和Linux下都有直接向堆申请空间的接口,windows下接口为VirtualAlloc,Linux下接口为brk。malloc是封装了irtualAlloc和brk在VirtualAlloc和brk之上,malloc本身其实也是一个内存池,该内存池是面向整个程序的。空间配置器对malloc进行封装 ,其是在malloc之上针对STL容器来设计的,空间配置器是一个内存池,该内存池仅面向STL的容器。
第二节中提到的几点不足之处,最主要还是:频繁向系统申请小块内存造成的。那什么才算是小块内存?SGI-STL以128字节作为小块内存与大块内存的分界线,将空间配置器其分为两级结构,一级空间配置器处理大块内存,二级空间配置器处理小块内存。下面我们根据STL配置器的源码来进行介绍。
一级空间配置器__malloc_alloc_template:
__malloc_alloc_template中allocate成员函数进行malloc的封装,deallocate成员函数进行free的封装,如下图所示。所以说一级空间配置器其实就是malloc和free,唯一的区别就是一级空间配置器中如果malloc空间失败会去调用oom_malloc函数。
oom_malloc函数的功能是先利用my_malloc_handler函数释放内存空间,如果函数指针my_malloc_handler为空则无法释放内存空间,抛异常,如果函数指针my_malloc_handler不为空则可以释放空间,调用my_malloc_handler释放空间然后再尝试malloc,如果malloc成功返回空间的指针结果。如下图所示。
总结:一级空间配置器和operator new很像,是malloc和free的封装,其特点是申请内存失败了抛异常。
二级空间配置器__default_alloc_template:
__default_alloc_template类中allocate成员函数中如果大于__MAX_BYTES,__MAX_BYTES是一个枚举常量为128,如果大于128则调用一级空间配置器来处理,如果小于128则在此处的二级空间配置器处理,如下图所示。
注:这里可以看出STL容器申请空间其实会直接找二级空间配置器,如果申请空间大于128字节才会跳到一级空间配置器中。
二级空间配置器allocate成员函数,如下图所示,free_list是一个哈希表。三个成员变量start_free、end_free和heap_size用来支持内存池,内存池是提前开辟好的128字节空间,其中start_free和end_free指针指向内存的头和尾。
每当需要申请128字节以内的空间时,让一个指针指向start_free指向的位置,然后start_free向后移动跳过此时申请空间的大小,将前面跳过的空间分配出去使用,最后如果这个内存池空间用完了,再去malloc开辟128字节空间作为新内存池即可。分配出去的某一小块内存不再使用回收后可以再次分配出去使用,如何管理分配出去小块内存的回收呢?
如果将回收的小块内存使用链表进行链接,再次申请空间时,通过链表依次查找回收的小块内存空间大小是否满足申请大小,满足则该小块内存空间可以再分配出去使用。使用链表进行管理依次查找的效率很低,可以使用哈希表来进行优化,对回收的小块内存进行管理。
那是否需要128桶个空间来管理用户已经归还的内存块呢?答案是不需要,因为用户申请 的空间基本都是4的整数倍,其他大小的空间几乎很少用到。因此SGI-STL将用户申请的内存块向上对齐到了8的整数倍,如上图所示哈希表,哈希表0号桶挂的是8字节的内存空间......15号桶挂的是128字节的内存空间。内存池空间申请的流程:申请小于128字节空间时,首先算出申请空间大小对应哈希表的几号桶(如果申请1-8字节空间则都找哈希表的0号桶,如果申请9-16字节空间则都找哈希表的1号桶......如果申请121-128字节空间则都找哈希表的15号桶),如果对应桶中有回收的小块内存空间则直接获取,如果对应桶中没有回收的小块内存空间,则去start_free和end_free指针管理的内存池获取空间,在内存池获取空间也是在申请空间大小的基础上向上对齐到8的整数倍获取空间。
注:申请小空间内存时往往会申请多个,因此这里编译器会进行一个优化,如果申请小于128字节空间时,首先是算出申请空间大小对应哈希表的几号桶,如果对应桶中没有回收的小块内存空间会去内存池获取空间,如果要申请的空间大小为n,这里往往会向内存池申请多个n空间大小,start_free跳过这多个n空间大小指向后面,第一个n空间地址返回,其余的n空间挂在哈希表中以备下次申请时使用。
__default_alloc_template类中deallocate成员函数和allocate成员函数相同,如果大于__MAX_BYTES,__MAX_BYTES是一个枚举常量为128,如果大于128则调用一级空间配置器来处理,如果小于128则在此处的二级空间配置器处理,如下图所示。
注:这里可以看出STL容器释放空间其实会直接找二级空间配置器,如果申请空间大于128字节才会跳到一级空间配置器中。
二级空间配置器deallocate成员函数中,如果申请的内存空间不再使用要释放,则根据要释放空间大小计算出哈希表对应的桶号,将要释放空间头插在哈希表对应的桶中。
问题:哈希表每个桶中挂的是一个单向链表,这里挂的是内存空间,内存空间如何像链表一样挂起来呢?
答:每一个内存空间如果要挂在哈希表中,首先要将指向该空间的指针强制转换成下图所示的联合体obj*类型,该空间前四个字节就存的是联合体指针free_list_link,free_list_link指向下一个被强转成联合体的空间地址,最后一个被强转成联合体的空间中前四个字节的free_list_link变量存的是空,这样就可以像链表一样链接起来。
当申请空间,哈希表桶中空间被拿走使用时,该空间所有的字节都可以随便使用,该空间释放时,再将该空间指针强转成联合体obj*类型,头插在哈希表对应桶中,并与桶中原本挂着的空间进行链接。
问题:SGI-STL将用户申请的内存块向上对齐到了8字节的整数倍,这里为什么是8字节的整数倍,而不是4字节的整数倍? 原因:哈希表的桶中要将空间的指针强制转换成联合体obj*类型,通过联合体的前四个字节的free_list_link指针进行空间链接。在32位机器下指针是4字节的,在64位机器下指针是8字节的,如果申请内存块向上对齐到了4字节的整数倍,而申请空间刚好为4字节的话这里就无法强转,因为free_list_link指针要占8字节空间,而申请空间本身才4字节。
一个进程里面应该只有一个空间配置器,因此空间配置器的类可以设置成单例模式的类,这样哈希表变量、管理内存池的各指针变量都对应只有一个,每次容器要开辟和释放空间时通过GetInstance函数调用allocate和deallocate成员函数即可。
如下图所示,这里源代码没有使用前面我们所学的单例模式,源代码是把所有的成员变量都设置为静态的,这样虽然可以创建多个对象,但一个进程中空间配置器类的每个成员变量都只有对应的一个,这也近似算是一种近似单例。
如果频繁申请小块内存,空间配置器相比于malloc,效率更高,且可以在一定程度内缓解内存碎片的问题。
内存碎片问题:
内存内碎片问题:申请下来的内存空间没有全部使用就是内碎片问题。空间配置器中将内存挂在哈希表中管理,申请空间时申请的内存块向上对齐到了8字节的整数倍,就会导致内碎片问题。
内存外碎片问题:在有足够的内存的条件下,频繁小块的内存空间申请,小块内存如果间隔的释放,那么可用内存被分隔开,就导致了内存的碎片化,因此无法开辟大块内存空间,这就是内存外碎片问题。内存外碎片问题其实就是频繁向内存申请小块内存空间导致的。
空间配置器缓解外碎片问题:空间配置器一次性开辟大块内存空间,将大块内存空间切割成小块内存空间挂在哈希表中,以满足自己的需求,这里小块内存空间是连续的,因此在一定程度内缓解内存外碎片的问题。
4.STL空间配置器的使用
以list为例如下图所示,list类模板的第二个参数就是空间配置器Alloc,缺省参数给的是alloc,那么alloc是什么呢?
如下图所示,alloc是STL库中二级的空间配置器,作为STL容器的默认空间配置器。如果容器申请空间大小大于128字节就会跳到STL库中的一级空间配置器,如果容器申请空间大小小于128字节就会继续执行二级空间配置器。
如下图一所示,在list类中使用simple_alloc类对二级空间配置器alloc进行封装,生成一个专门针对list_node的空间申请类list_node_allocator。如下图二所示是simple_alloc类中对alloc的封装,分别调用了二级空间配置器alloc的allocate函数和deallocate函数进行二级空间适配器的空间申请和释放。
如下图三所示,在list类中get_node和put_node调用封装的空间申请类list_node_allocator中allocate函数和deallocate函数进行节点空间的申请和释放。
如下图三所示,在create_node函数中,先调用get_node函数申请节点空间,因为get_node只申请了节点空间没有初始化,所以调用construct函数针对新申请的节点的data进行初始化,construct函数中使用了定位new来调用data对应类型的构造函数进行初始化工作(因为data可能是自定义类型的变量),如下图四所示。
如下图三所示,destroy_node函数中显式的调用data对应类型的析构函数,并调用put_node函数将节点释放掉。
注:STL各容器模板的Alloc参数可以传自己实现的空间适配器类,只要空间适配器类中定义实现了allocate函数和deallocate函数即可。
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧
文章名称:c++-第25节-STL之空间配置器-创新互联
转载注明:http://cdiso.cn/article/cocdje.html