STL-空间配置器的了解

前言

        空间配置器,顾名思义就是为了各个容器高效的管理空间(空间的申请与回收)的,在默默的工作的。虽然在常规上使用STL时,可能用不上它,但是站在学习研究的角度,学习它的实现原理对我们有很大的帮助。

目录

1.为什么需要空间配置器

2.SGI—STL空间配置器实现原理

3.一级空间配置器实现原理

        3.1 一级空间配置器

        3.2 二级空间配置器

                3.2.1内存池

                3.2.2 SGI-STL中二级空间配置器设计

        3.3 SGI-STL二级空间配置器之空间申请

                3.3.1前期的准备

                3.3.2申请空间

                        3.3.2.1填充内存块

                        3.3.2.2向内存池中索要空间

                3.3.3空间的回收

        3.4 空间配置器的默认选择

        3.5 空间配置器的再次封装

        3.6 对象的构造和释放

        


1.为什么需要空间配置器

        new申请的空间,虽然可以使用,但是有以下不足之处:

        1.空间申请与释放需要用户自己管理,容易造成内存泄露 

        2.频繁向系统申请小块内存块,容易造成内存碎片

        3.频繁向系统系统申请小块内存,影响程序运行效率

        4.直接使用malloc和new进行申请每块空间前有额外空间浪费

        5.申请失败如何应对

        6.代码结构比较混乱,代码复用率不高

        7.未考虑线程安全问题

        由于存在这些问题,因此需要一块高效的的内存管理机制。 

2.SGI—STL空间配置器实现原理

        频繁的向系统申请小块内存会有很多的问题,那么怎么样的内存算是小块内存呢?

        SGI-STL以128作为大内存和小内存的分界线,将空间配置器分为两级结构,一级空间配置器处理大块内存,二级空间配置器处理小块内存。 

3.一级空间配置器实现原理

        3.1 一级空间配置器

        一级空间配置器的原理非常简单,直接对malloc和free进行了封装,并增加了C++中set_new_handle思想。

namespace qyy
{template <int inst>class __malloc_alloc_template{private:static void* oom_malloc(size_t);public://对malloc进行封装static void* allocate(size_t n){//申请空间成功直接返回,失败了交给oom_malloc处理void* result = malloc(n);if (result == 0){result = oom_malloc(n);}return result;}//对free进行封装static void* deallocate(void* p, size_t){free(p);}//模拟实现set_malloc_handler//该函数参数为函数指针,返回值也是函数指针//void(*set_malloc_handler(void*(f*)( )  )) ()static void (*set_malloc_hander(void (*f)()))(){void (*old)() = __malloc_alloc_oom_handler;__malloc_alloc_oom_handler = f;return (old);}};//malloc空间申请失败时调用该函数template<int inst>void* __malloc_alloc_template<inst>::oom_malloc(size_t){void(*my_malloc_handler)();void* result;for (;;){//检测用户是否设置空间不足应对措施,如果没有设置,抛异常模拟new的方式my_malloc_handler = __malloc_alloc_handler;if (0 == my_malloc_handler){__THROW_BAD_ALLOC;}//如果设置了,执行用户提供空间不足应对措施(*my_malloc_handler)();//继续申请空间可能就会成功result = malloc(n);if (result)return (result);}}typedef __malloc_alloc_template<0> malloc_alloc;}

        3.2 二级空间配置器

        二级空间配置器是专门负责处理小于128字节的小块空间。如何才能提升小块内存的的申请和释放的方式呢?SGI-STL采用了内存池的技术来提高申请空间的速度以及减少额外的空间的浪费,采用哈希桶的方式来提高用户获取空间的速度与高效管理。 

                3.2.1内存池

        内存池就是:先申请一块比较大的内存当做备用,当需要内存时,直接去内存池中拿内存就好了,当内存池中的空间不够时,再向内存中取,当用户不用时,直接还回内存池即可。避免了频繁的向系统申请小块内存所造成的效率低,内存碎片以及额外浪费的问题。 

                3.2.2 SGI-STL中二级空间配置器设计

        SGI-STL中的二级空间配置器使用了内存池技术,但是没有采用链表的方式对用户已归还的空间进行管理(因为用户申请空间时是在查找合适的小块内存时效率较低),而采取哈系桶的方式进行管理。那么是否128个桶来管理已归还的空间呢?答案是不需要的,因为用户申请空间基本上都是按照4的整数倍来申请的,其他大小的空间基本很少用到,因此:SGI-STL将用户申请的内存块向上对其到了8的整数倍。  

        3.3 SGI-STL二级空间配置器之空间申请

                3.3.1前期的准备

namespace qyy
{template<int inst>class __default_alloc_template{private:enum {__ALIGN = 8};//如果用户需要的内存不是8的整数倍,向上对其到8的整数倍enum {__MAX_BYTES = 128};//大小块内存的分界线enum { __NFREELISTS = __MAX_BYTES / __ALIGN };//采用哈希桶保存小块内存时所需要的内存个数static size_t ROUND_UP(size_t bytes){return (((bytes)+__ALIGN - 1) & ~(ALIGN - 1));}private://用联合图来维护链表结构union obj{union obj* free_list_link;char client_data[1];};private:static obj* free_list[__NFREELISTS];//哈希函数根据用户提供的字节数找到对应的桶号static size_t FREELIST_INDEX(size_t bytes){return (((bytes)+__ALIGN - 1) / ALING - 1);}//start_free 和end_free用来标记内存池中大块内存的起始和末尾位置static char* start_free;static char* end_free;//用来记录该空间配置器已经向系统索要了多少的块内存static size_t heap_size;///...};
}

                3.3.2申请空间

//函数功能:向空间配置器索要空间//参数n:所需要的空间字节数//返回值:返回空间的首地址static void* allocate(size_t n){obj* __VOLATILE* my_free_list;obj* __RESTRICT result;//检测用户所需要的空间是否在超过128字节(即是否为小块内存)if (n > (size_t)__MAX_BYTES){//不是小块内存交给一级空间配置器来处理return (malloc_alloc::allocate(n));}//根据用户所需要的字节找到对应的桶号my_free_list = free_list + FREELIST_INDEX(n);result = *my_free_list;//如果桶里面没有内存块,向桶里面补充内存块if (result == 0){//n将向上对齐到8的整数倍,保证向桶里面补充内存块时,内存块一定是8的整数倍void* r = refill(ROUND_UP(n));return r;}//维护桶里面剩余内存块的链式关系*my_free_list = result->free_lsit_link;return (result);}

                        3.3.2.1填充内存块

		//函数功能:向哈系桶里面补充空间//参数n小块内存//首个小块内存的首地址template<int inst>void* __default_alloc_template<inst>::refill(size_t n){//一次性向内存池索要20个n字节的空间int nobjs = 20;char* chunk = chunk_alloc(n, nobjs);obj** my_free_list;obj* result;obj* current_obj, * next_obj;int i;//如果只有一块直接返回给用户if (1 == nobjs)return (chunk);//找到对应的桶号my_free_list = free_list + FREELIST_INDEX(n);//将第一块返回给用户,去其他的挂到哈系桶中result = (obj*)chunk;*my_free_list = next_obj = (obj*)(chunk + n);for (i = 1;;i++){current_obj = next_obj;next_obj = (obj*)((char*)next_obj + n);if (nobjs - 1 == i){current_obj->free_list_link = 0;break;}else{current_obj->free_list_link = next_obj;}}return (result);}

                        3.3.2.2向内存池中索要空间

 

	    template<int inst>char* __default_alloc_tempalte<inst>::chunk_alloc(size_t size, int& nobjs){//计算nobjs个size字节内存块的总大小以及内存池中剩余空间的总大小char* result;size_t total_bytes = size * nobjs;size_t bytes_left = end_free - start_free;//如果内存池可以提供total_bytes字节,返回if (bytes_left >= tatal_bytes){result = start_free;start_free += tatal_bytes;return (result);}else if (bytes_left >= size){//nobjs块无法提供,但是至少可以提供1块size字节的内存块,提供后返回nobjs = bytes_left / size;tatal_byte = size * nobjs;result = start_free;start_free += tatal_bytes;return (result);}else{//内存池空间不够连一小块内存都没有。//向系统堆求助,往内存池中补充空间//计算向内存中补充空间的大小:本次空间总大小的两倍 + 向系统申请总大小/16size_t bytes_to_get = 2 * tatal_byte + ROUND_UP(heap_size >> 4);//如果内存池有剩余空间(该空间一定是8的整数倍)将该空间挂到对应的哈希桶中if (bytes_left > 0){//找到对应的哈系桶obj** my_free_list = free_list + FREELIST_INDEX(bytes_left);((obj*)start_free)->free_list_link = *my_free_list; )* my_ree_list = (obj*)start_free;}//通过系统堆向内存池补充空间,如果补充成功,递归继续分配start_free = (char*)malloc(bytes_to_get);if (0 == start_free){//通过系统堆补充失败,在哈系桶中寻找是否有没有使用的较大块内存int i;obj** my_free_list, * p;for (i = size; i <= __MAX_BYTES;i += __ALIGN){my_free_list = free_list + FREELIST_INDEX(i);p = *my_free_list;//如果有,将该内存块补充进内存池,递归继续分配if (0 != p){*my_free_list = p->free_list_link;start_free = (char*)p;end_free = start_free + i;return (chunk_alloc(size, nobjs));}}//山穷水尽,只能向一级空间配置器求助//注意:此处一定要将end_free置空,因为一级空间配置器一旦抛异常就会出问题end_free = 0;start_free = (char*)malloc_alloc::allocate(bytes_to_get);}heap_size += bytes_to_get;end_free = start_free + bytes_to_get;return (chunk_alloc(size, nobjs));}}

                3.3.3空间的回收

		//函数功能:用户将空间归还给空间配置器//参数:p空间首地址 n空间总大小static void deallocate(void* p, size_t n){obj* q = (obj*)p;obj** my_free_list;if (n > (size_t)__MAX_BYTES){malloc_alloc::deallocate(p, n);return;}//找到对应的哈系桶,将内存挂到哈系桶中my_free_list=free_list+ FREELIST_INDEX(n);q->free_list_link = *my_free_list;*my_free_list = q;}

        3.4 空间配置器的默认选择

        SIG_STL默认使用一级空间配置器 还是二级空间配置器,是通过宏开关USE_MALLOC宏进行控制的。

         在SGI_STL中没有定义这个宏所以,默认情况下SGI_STL使用二级空间配置器。

        3.5 空间配置器的再次封装

        在C++中,用户所需要的空间可能是任意类型的,有单个对象的空间,有连续空间,每一次都让用户自己计算空间的总大小不是很友好,因此SGI_STL将空间配置器重新封装了一层。

template<class T,class ALLOC>class simlpe_alloc{public://申请n个T类型对象大小的空间static T* allocate(size_t n){return 0 == n ? 0 : (T*)ALLOC::allocte(n * sizeof(T));}//申请一个T类型对象大小的空间static T* allocate(void){return (T*)ALLOC::allocate(sizeof(T));}//释放n个T类型对象大小的空间static void deallocate(T* p, size_t n){if (0 != n){ALLOC::deallocate(p, n * sizeof(T));}}static void deallocate(T* p){ALLOC::deallocate(p, sizeof(T));}

 

        3.6 对象的构造和释放

        因为效率的需要,SGI_STL决定将空间的申请和释放和对象的构造析构两个过程分离,因为有些对象的构造不需要调用构造函数,销毁时不需要调用析构函数,将该过程分离可以提高程序的性能。

	//归还空间时,先调用析构函数进行资源的清理template <class T>inline void destory(T* pointer){pointer->~T();}//空间申请好后,调用该函数:利用placement-new完成对对象的构造template<class T1,class T2>inline void construct(T1* p, const T2& value){new(p)T1(value);}

         注意,在释放对象时,需要根据对象的类型来确定是否需要调用析构函数(类型萃取)

        对象的类型可以通过迭代器萃取到。

        这些内容难度较大作为了解就可以了。(对于初学者来说) 

 

        

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/86416.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL项目迁移华为GaussDB PG模式指南

文章目录 0. 前言1. 数据库模式选择&#xff08;B/PG&#xff09;2.驱动选择2.1. 使用postgresql驱动2.1. 使用opengaussjdbc驱动 3. 其他考虑因素4. PG模式4.1 MySQL和OpenGauss不兼容的语法处理建议4.2 语法差异 6. 高斯数据库 PG模式JDBC 使用示例验证6. 参考资料 本章节主要…

什么是JVM ?

目录 一、JVM 简介 1.1 JVM 发展史 1.Sun Classic VM 2.Exact VM 3.HotSpot VM 4.JRockit 5.J9 JVM 6.Taobao JVM&#xff08;国产研发&#xff09; 1.2 JVM 和《Java虚拟机规范》 二、 JVM 运行流程 JVM 执行流程 三、JVM 运行时数据区 3.1 堆&#xff08;线程共享…

java八股文面试[JVM]——垃圾回收

参考&#xff1a;JVM学习笔记&#xff08;一&#xff09;_卷心菜不卷Iris的博客-CSDN博客 GC垃圾回收面试题&#xff1a; JVM内存模型以及分区&#xff0c;需要详细到每个区放什么 堆里面的分区&#xff1a;Eden&#xff0c;survival from to&#xff0c;老年代&#xff0c;各…

git分支管理策略

git的基础操作以及常用命令在上篇博客哦~ git原理与基本使用 1.分支管理 1.主分支 在版本回退⾥&#xff0c;我们已经知道&#xff0c;每次提交&#xff0c;Git都把它们串成⼀条时间线&#xff0c;这条时间线就可以理解为是⼀个分⽀。截⽌到⽬前&#xff0c;只有⼀条时间线&…

vue2 路由进阶,VueCli 自定义创建项目

一、声明式导航-导航链接 1.需求 实现导航高亮效果 如果使用a标签进行跳转的话&#xff0c;需要给当前跳转的导航加样式&#xff0c;同时要移除上一个a标签的样式&#xff0c;太麻烦&#xff01;&#xff01;&#xff01; 2.解决方案 vue-router 提供了一个全局组件 router…

实时记录开房信息,在线开房记录查询工具

随着社会的高速发展&#xff0c;异地出差人士越来越多&#xff0c;往往全国跑&#xff0c;每每去到一个地区都要开房休息&#xff0c;当开房数量越来越多的时候&#xff0c;往往会把数据混乱&#xff0c;不利于回公司后的出差费用报销&#xff0c;故此发现了一款实时记录实时查…

第七周第六天学习总结 | MySQL入门及练习学习第一天

实操练习&#xff1a; 1.查看数据库 2.查询当前数据库 3.创建一个名为 lianxi 的数据库 4.删除一个名为 shanchu 的数据库&#xff08;如果存在就删除&#xff0c;不存在不删除&#xff09; 5.切换至 lianxi 数据库下&#xff0c;并验证 6.查询 lainxi 数据库下的所有数据…

算法leetcode|74. 搜索二维矩阵(rust重拳出击)

文章目录 74. 搜索二维矩阵&#xff1a;样例 1&#xff1a;样例 2&#xff1a;提示&#xff1a; 分析&#xff1a;题解&#xff1a;rust&#xff1a;go&#xff1a;c&#xff1a;python&#xff1a;java&#xff1a; 74. 搜索二维矩阵&#xff1a; 给你一个满足下述两条属性的…

vscode调试PHP代码

目录 准备工作ssh的连接以及配置调试 准备工作 1.首先你需要下载一个vscode 2.下载模块 你需要在VScode中去下载我们所需的两个模块PHP Debug以及remote -ssh 3.安装对应版本的xdebug 需要在xdebug的官方去进行分析&#xff0c;选择适合你自己版本的xdebug 去往官方&#x…

SQLI-labs-第一关

知识点&#xff1a;单引号字符型注入 思路&#xff1a; 1、根据提示&#xff0c;为get注入&#xff0c;在url中输入内容 2、判断注入点 输入?id1&#xff0c;显示数据库语句错误&#xff0c;说明这里存在sql注入漏洞 输入?id1‘ and 11 -- &#xff0c;回显正常&#xff0c…

centos服务器系统下安装python3并与自带的python2

centos服务器系统下安装python3并与自带的python2 在centos中&#xff0c;自带有python2&#xff0c;因此需要经常安装python3。但是这里有一个坑&#xff0c;就是centos的yum是用python2写的&#xff0c;如果正常编译安装python3&#xff0c;那么yum就会直接挂了。为了方便以…

11. 网络模型保存与读取

11.1 网络模型保存(方式一) import torchvision import torch vgg16 torchvision.models.vgg16(pretrainedFalse) torch.save(vgg16,"./model/vgg16_method1.pth") # 保存方式一&#xff1a;模型结构 模型参数 print(vgg16) 结果&#xff1a; VGG((feature…