当前位置: 首页 > news >正文

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);}

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

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

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

 

        

相关文章:

STL-空间配置器的了解

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

哔哩哔哩 B站 bilibili 视频视频音效调节 清澈人声

视频音效调节方式&#xff1a;直接视频播放内容界面内鼠标右键点击视频音效调节 注意&#xff1a;需要使用的是谷歌浏览器&#xff0c;我的火狐浏览器试了不行&#xff0c;都没选项&#xff0c;火狐的出来的界面是这样的&#xff1a; 目录 具体操作如下&#xff1a; 1、谷歌…...

下一代存储解决方案:湖仓一体

文章首发地址 湖仓一体是将数据湖和数据仓库相结合的一种数据架构&#xff0c;它可以同时满足大数据存储和传统数据仓库的需求。具体来说&#xff0c;湖仓一体可以实现以下几个方面的功能&#xff1a; 数据集成&#xff1a; 湖仓一体可以集成多个数据源&#xff0c;包括结构…...

IntelliJ IDEA 2023.2.1 修复版本日志

我们刚刚发布了 v2023.2 的第一个错误修复更新。 您可以从 IDE 内部、使用工具箱应用程序或通过快照&#xff08;如果您使用的是 Ubuntu&#xff09;更新到此版本。您也可以直接从我们的网站下载。 以下是最新版本中包含的最值得注意的改进和修复的列表&#xff1a; 我们已经解…...

算法通关村十三关 | 数组字符串加法专题

1. 数组实现整数加法 题目&#xff1a;LeetCode66&#xff0c;66. 加一 - 力扣&#xff08;LeetCode&#xff09; 思路 我们只需要从头到尾依次运算&#xff0c;用常量标记是否进位&#xff0c;需要考虑的特殊情况是digits [9,9,9]的时候进位&#xff0c;我们组要创建长度加1…...

k8s--基本概念理解

必填字段 在要创建的 Kubernetes 对象的文件中.yaml&#xff0c;您需要设置以下字段的值&#xff1a; apiVersion- 您使用哪个版本的 Kubernetes API 创建此对象 kind- 你想创建什么样的对象 metadata- 有助于唯一标识对象的数据&#xff0c;包括name字符串、UID和可选namesp…...

流媒体开发千问【持续更新】

H.264中IDR帧和I帧区别 H.264/AVC编码标准中&#xff0c;IDR帧和I帧都是关键帧&#xff0c;即它们都不依赖于其他帧进行解码。但是&#xff0c;它们之间存在明确的区别&#xff1a; 定义与功能&#xff1a; I帧&#xff08;Intra-frame&#xff09;&#xff1a;I帧是一个内部编…...

全球各国官方语言大盘点,英语不得不学哇。。。

因国家和地区范围界定不同&#xff0c;官方语言只是个相对概念。具体而言是一个国家通用的正式语言或认定的正式语言。它是为适应管理国家事务的需要&#xff0c;在国家机关、正式文件、法律裁决及国际交往等官方场合中规定一种或几种语言为有效语言的现象。官方语言也是一个国…...

【mq】如何保证消息可靠性

文章目录 mq由哪几部分组成rocketmqkafka 为什么需要这几部分nameserver/zookeeper可靠性 broker可靠性 生产者消费者 mq由哪几部分组成 rocketmq kafka 这里先不讨论Kafka Raft模式 比较一下&#xff0c;kafka的结构和rocketmq的机构基本上一样&#xff0c;都需要一个注册…...

疲劳检测-闭眼检测(详细代码教程)

简介 瞌睡经常发生在汽车行驶的过程中&#xff0c;该行为害人害己&#xff0c;如果有一套能识别瞌睡的系统&#xff0c;那么无疑该系统意义重大&#xff01; 实现步骤 思路&#xff1a;疲劳驾驶的司机大部分都有打瞌睡的情形&#xff0c;所以我们根据驾驶员眼睛闭合的频率和…...

大数据日常运维命令

1、HDFS NameNode /usr/local/fqlhadoop/hadoop/sbin/hadoop-daemon.sh start namenode /usr/local/fqlhadoop/hadoop/sbin/hadoop-daemon.sh stop namenode bin/hdfs haadmin -DFSHAAdmin -getServiceState n1 2、HDFS DataNode /usr/local/fqlhadoop/hadoop/sbin/hadoop-…...

解锁安全高效办公——私有化部署的WorkPlus即时通讯软件

在当今信息时代&#xff0c;高效的沟通与协作对于企业的成功至关重要。然而&#xff0c;随着信息技术的发展&#xff0c;保护敏感信息和数据安全也变得越来越重要。为了满足企业对于安全沟通和高效办公的需求&#xff0c;我们隆重推出私有化部署的WorkPlus即时通讯软件&#xf…...

IDEA使用git

文章目录 给所有文件配置git初始化本地仓库创建.gitignore文件添加远程仓库分支操作 给所有文件配置git 初始化本地仓库 创建.gitignore文件 添加远程仓库 分支操作 新建分支 newbranch 切换分支 checkout 推送分支 push 合并分支 merge...

【跟小嘉学 Rust 编程】十八、模式匹配(Patterns and Matching)

系列文章目录 【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据 【跟小嘉学…...

keepalived+lvs+nginx高并发集群

keepalivedlvsnginx高并发集群 简介&#xff1a; keepalivedlvsnginx高并发集群&#xff0c;是通过LVS将请求流量均匀分发给nginx集群&#xff0c;而当单机nginx出现状态异常或宕机时&#xff0c;keepalived会主动切换并将不健康nginx下线&#xff0c;维持集群稳定高可用 1.L…...

剑指Offer65.不用加减乘除做加法 C++

1、题目描述 写一个函数&#xff0c;求两个整数之和&#xff0c;要求在函数体内不得使用 “”、“-”、“*”、“/” 四则运算符号。 示例: 输入: a 1, b 1 输出: 2 2、VS2019上运行 使用位运算的方法 #include <iostream>class Solution { public:/*** 计算两个整…...

【linux命令讲解大全】004.探索Linux命令行中的chmod和chown工具

文章目录 chmod概要主要用途参数选项返回值例子 chown补充说明语法选项参数实例 从零学 python chmod 用来变更文件或目录的权限 概要 chmod [OPTION]... MODE[,MODE]... FILE... chmod [OPTION]... OCTAL-MODE FILE... chmod [OPTION]... --referenceRFILE FILE...主要用途…...

nginx会话保持

ip_hash:通过IP保持会话 作用&#xff1a; nginx通过后端服务器地址将请求定向的转发到服务器上。 将客户端的IP地址通过哈希算法加密成一个数值 如果后端有多个服务器&#xff0c;第一次请求到服务器A&#xff0c; 并在务器登录成功&#xff0c;那么再登录B服务器就要重新…...

SpringBoot使用Druid连接池 + 配置监控页面(自定义版 + starter版)

目录 1. Druid连接池的功能2. 自定义版2.1 pom.xml添加依赖2.2 MyDataSourceConfig实现2.3 application.properties配置编写Controller进行测试2.4 druid监控页面查看 3. starter版3.1 pom.xml添加依赖3.2 自动配置分析3.3 使用application.properties对druid进行配置3.4 druid…...

【业务功能篇77】微服务-OSS对象存储-上传下载图片

3. 图片管理 文件存储的几种方式 单体架构可以直接把图片存储在服务器中 但是在分布式环境下面直接存储在WEB服务器中的方式就不可取了&#xff0c;这时我们需要搭建独立的文件存储服务器。 3.1 开通阿里云服务 针对本系统中的相关的文件&#xff0c;图片&#xff0c;文本等…...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

Unity3D中Gfx.WaitForPresent优化方案

前言 在Unity中&#xff0c;Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染&#xff08;即CPU被阻塞&#xff09;&#xff0c;这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案&#xff1a; 对惹&#xff0c;这里有一个游戏开发交流小组&…...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统

医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上&#xff0c;开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识&#xff0c;在 vs 2017 平台上&#xff0c;进行 ASP.NET 应用程序和简易网站的开发&#xff1b;初步熟悉开发一…...

在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:

在 HarmonyOS 应用开发中&#xff0c;手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力&#xff0c;既支持点击、长按、拖拽等基础单一手势的精细控制&#xff0c;也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档&#xff0c…...

【入坑系列】TiDB 强制索引在不同库下不生效问题

文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

服务器硬防的应用场景都有哪些?

服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式&#xff0c;避免服务器受到各种恶意攻击和网络威胁&#xff0c;那么&#xff0c;服务器硬防通常都会应用在哪些场景当中呢&#xff1f; 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面

代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口&#xff08;适配服务端返回 Token&#xff09; export const login async (code, avatar) > {const res await http…...

【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)

1.获取 authorizationCode&#xff1a; 2.利用 authorizationCode 获取 accessToken&#xff1a;文档中心 3.获取手机&#xff1a;文档中心 4.获取昵称头像&#xff1a;文档中心 首先创建 request 若要获取手机号&#xff0c;scope必填 phone&#xff0c;permissions 必填 …...

使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台

🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...

GO协程(Goroutine)问题总结

在使用Go语言来编写代码时&#xff0c;遇到的一些问题总结一下 [参考文档]&#xff1a;https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现&#xff1a; 今天在看到这个教程的时候&#xff0c;在自己的电…...