STL-空间配置器的了解

news2024/11/26 10:44:37

前言

        空间配置器,顾名思义就是为了各个容器高效的管理空间(空间的申请与回收)的,在默默的工作的。虽然在常规上使用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
			{
				//内存池空间不够连一小块内存都没有。
				//向系统堆求助,往内存池中补充空间
				//计算向内存中补充空间的大小:本次空间总大小的两倍 + 向系统申请总大小/16
				size_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.coloradmin.cn/o/938815.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python+Yolov8手势特征识别检测

程序示例精选 PythonYolov8手势特征识别检测 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<PythonYolov8手势特征识别检测>>编写代码&#xff0c;代码整洁&#xff0c;规则&am…

Matlab论文插图绘制模板第109期—特征渲染的标签气泡散点图

在之前的文章中&#xff0c;分享了Matlab标签散点图的绘制模板&#xff1a; 特征渲染的标签散点图&#xff1a; 进一步&#xff0c;再来分享一下特征渲染的标签气泡散点图的绘制模板&#xff0c;从而可以再添加一个维度的信息。 先来看一下成品效果&#xff1a; 特别提示&…

python之socket编程

本章内容 1、socket 2、IO多路复用 3、socketserver Socket socket起源于Unix&#xff0c;而Unix/Linux基本哲学之一就是“一切皆文件”&#xff0c;对于文件用【打开】【读写】【关闭】模式来操作。socket就是该模式的一个实现&#xff0c;socket即是一种特殊的文件&#xff0…

简单工厂模式概述和使用

目录 一、简单工厂模式简介1. 定义2. 使用动机 二、简单工厂模式结构1.模式结构2. 时序图 三、简单工厂的使用实例四、简单工厂模式优缺点五、简单工厂模式在Java中的应用 一、简单工厂模式简介 原文链接 1. 定义 简单工厂模式(Simple Factory Pattern)&#xff1a;又称为静…

Remmina在ubuntu22.04中无法连接Windows

Remmina在ubuntu22.04中无法连接Windows 问题 提示为&#xff1a; 无法通过TLS到RDP服务器… 分析 原因是Remmina需要使用openssl通过RDP加密与Windows计算机连接&#xff0c;而ubuntu22.04系统中OpenSSL版本为3.0&#xff0c;Openssl3 将 tls<1.2 和 sha1 的默认安全级别…

checkstyle检查Java编码样式:源文件和文本文件末尾要有一个空行

背后的原理&#xff1a; 源文件和文本文件末尾应该有一个行分隔符&#xff0c;以便于他人在后面增加新的内容&#xff0c;而“diff”命令不会将前面的行作为改动过来显示。 https://checkstyle.sourceforge.io/checks/misc/newlineatendoffile.html#NewlineAtEndOfFile 例如&a…

外部链接和VUE项目互相跳转实现退出登录

一、补坑总结提前说明 我的项目有一个前台MVC项目门户站点(用户登录信息存储在cookie)&#xff0c;和后台vue项目&#xff08;用户登录信息存储在localstorage&#xff09;站点&#xff0c;2个站点分属不同的端口&#xff0c;那就是跨域了。之前我已经实现过了某个站点登录后&a…

Linux 桌面上的 Firefox 面临着大问题

导读毫无疑问&#xff0c;无论是在桌面、笔记本电脑还是移动设备上&#xff0c;浏览器都是任何操作系统中最重要的应用之一。 如果没有一个功能强大、快速且稳定的浏览器&#xff0c;操作系统的实用性将大幅度降低&#xff0c;以至于我相当确定&#xff0c;如果一个操作系统没有…

C语言二——依次将10个数输入,要求将其中最大的数输出

这是一个简单的C语言程序&#xff0c;它会接受用户输入的10个整数&#xff0c;然后找出最大值并输出。 程序的执行步骤如下&#xff1a; 声明一个数组 n&#xff0c;用于存储用户输入的10个整数&#xff0c;声明一个变量 i 和 t。提示用户输入10个数。使用 for 循环&#xff…

Face2Face: Real-time Face Capture and Reenactment of RGB Videos (CVPR 2016) 译文

建议的在线再现设置:单目目标视频序列(例如&#xff0c;来自Youtube)基于源演员的表达进行再现&#xff0c;源演员使用商品网络摄像头进行现场录制。 摘要 我们提出了一种用于单目目标视频序列(例如Youtube视频)的实时面部再现的新方法。源序列也是单目视频流&#xff0c;用商…

小红书口碑营销技巧有哪些,打造独特品牌内容

作为一款社交电商平台&#xff0c;从目前来看&#xff0c;其实小红书也算是比较不错的口碑营销传播平台了。今天就来带大家一起分享下&#xff0c;小红书口碑营销技巧有哪些&#xff0c;打造独特品牌内容&#xff01; ​ 一、小红书口碑营销怎么做 1. 了解目标用户 首先需要明…

不到3个月,快速上线联创三金MES系统,数字化生产管理初见成效

PART 01 MES系统上线 8月25日&#xff0c;惠州联创三金科技有限公司&#xff08;下文简称联创三金&#xff09;顺利举行了MES系统上线仪式&#xff0c;这标志着联创三金正式运行全新的数字化生产管理系统&#xff0c;迈向了生产管理全面透明化的新阶段。联创集团IT总监邓燕琼…

【Vue3】Pinia状态管理

Pinia pinia与Vuex的对比pinia的介绍pinia与Vuex区别&#xff1a;pinia的使用使用准备步骤一&#xff1a;新建文件导入pinia使用准备步骤二&#xff1a;挂载pinia 创建pinia的Store新建页面。创建对应的Store页面使用pinia的store Pinia核心概念State操作State一重置state改变s…

SciencePub学术 | 计算机智能类重点SCIEEI征稿中

SciencePub学术 刊源推荐: 计算机智能类重点SCIE&EI征稿中&#xff01;信息如下&#xff0c;录满为止&#xff1a; 一、期刊概况&#xff1a; 计算机智能类重点SCIE&EI 【期刊简介】IF&#xff1a;1.0-1.5&#xff0c;JCR4区&#xff0c;中科院4区&#xff1b; 【检…

Google Play商店优化排名因素之应用标题

对于用户数量和应用数量而言&#xff0c;Google Play 商店是最大的应用商店。市场巨大&#xff0c;同时机遇也巨大。然而由于有近 250 万个应用程序&#xff0c;竞争也同样激烈。 1、应用程序标题是所有排名因素中最强的。 与Apple的30个字符限制相反&#xff0c;Google Play商…

初识Java 1-1 面向对象的语言

目录 引用的作用 数据的储存 常见的数据储存方式 特殊储存的基本类型 数组 销毁对象 基本类型的作用域 对象的作用域 创建新类型 - class关键字 方法、参数和返回值 参数列表 编写程序 名称可见性 使用组件 static关键字 Java程序 编程风格&#xff08;驼峰式…

SAP从放弃到入门系列之abapGit更新与卸载

目录概览 概述一、更新1.1 独立版本1.2 开发者版本1.2.1 在线更新1.2.2 离线更新 1.3 更新版本常见问题 二、卸载abapGit 概述 我们就以更新abapGit为例,通过abapGit管理我们的代码其实操作时相似的&#xff0c;更新独立版本就有点像最原始的没有版本管理工具时的版本管理方式…

Elasticsearch中倒排索引、分词器、DSL语法使用介绍

&#x1f353; 简介&#xff1a;java系列技术分享(&#x1f449;持续更新中…&#x1f525;) &#x1f353; 初衷:一起学习、一起进步、坚持不懈 &#x1f353; 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正&#x1f64f; &#x1f353; 希望这篇文章对你有所帮助,欢…

研磨设计模式day14模板方法模式

目录 场景 原有逻辑 有何问题 解决方案 解决思路 代码实现 重写示例 模板方法的优缺点 模板方法的本质 何时选用 场景 现在模拟一个场景&#xff0c;两个人要登录一个系统&#xff0c;一个是管理员一个是用户&#xff0c;这两个不同身份的登录是由后端对应的两个接…

Metasploit“MSF”连接postgresql时因排序规则版本不匹配导致无法连接

一、问题 更新Kali之后使用Metasploit时出现一个问题&#xff0c;连接postgresql时因排序规则版本不匹配导致无法连接 警告: database "msf" has a collation version mismatch DETAIL: The database was created using collation version 2.36, but the operati…