STL-内存的配置与释放

STL-内存的配置与释放

STL有两级空间配置器,默认是使用第二级。第二级空间配置器会在某些情况下去调用第一级空间配置器。空间配置器都是在allocate函数内分配内存,在deallocate函数内释放内存。

第一级空间配置器

第一级配置器只是对malloc函数和free函数的简单封装,在allocate内调用malloc,在deallocate内调用free。同时第一级配置器的oom_malloc函数,用来处理malloc失败的情况。如下所示:

allocate对malloc函数简单封装 :

static void *allocate(size_t n)
{void *result = malloc(n);if (NULL == result)result = oom_malloc(n);return result;
}

deallocate对free函数简单封装 :

static void deallocate(void *p, size_t) { free(p); }

oom_malloc调用外部提供的malloc失败处理函数,然后重新试着再次调用malloc。重复执行此过程,直到malloc成功为止 :

template <int inst>
void* __malloc_alloc<inst>::oom_malloc(size_t n)
{void (*my_malloc_handler)();void *result;for (;;){my_malloc_handler = malloc_alloc_oom_handler;if (NULL == my_malloc_handler)__THROW_BAD_ALLOC;(*my_malloc_handler)();result = malloc(n);if (result)return result;}
}

第二级空间配置器

第一级配置器直接调用malloc和free带来了几个问题:

  1. 内存分配/释放的效率低。

  2. 当配置大量的小内存块时,会导致内存碎片比较严重。

  3. 配置内存时,需要额外的部分空间存储内存块信息,所以配置大量的小内存块时,还会导致额外内存负担。

第二级配置器维护了一个自由链表数组,每次需要分配内存时,直接从相应的链表上取出一个内存节点就完成工作,效率很高。

自由链表数组

自由链表数组其实就是个指针数组,数组中的每个指针元素指向一个链表的起始节点。数组大小为16,即维护了16个链表,链表的每个节点就是实际的内存块,相同链表上的内存块大小都相同,不同链表的内存块大小不同,从8一直到128。如下所示,obj为链表上的节点,free_list就是链表数组。

//自由链表
union obj
{union obj *free_list_link;char data[1];
};
//自由链表数组
static obj *volatile free_list[__NFREELISTS];

内存分配

allocate函数内先判断要分配的内存大小,若大于128字节,直接调用第一级配置器,否则根据要分配的内存大小从16个链表中选出一个链表,取出该链表的第一个节点。若相应的链表为空,则调用refill函数填充该链表。如下:

template <bool threads>
void *__default_alloc<threads>::allocate(size_t n)
{obj *volatile *my_free_list;obj *result;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 == NULL){//第n号链表无内存块,则准备重新填充该链表void *r = refill(ROUND_UP(n));return r;}*my_free_list = result->free_list_link;return result;
}

填充链表

若allocate函数内要取出节点的链表为空,则会调用refill函数填充该链表。

refill函数内会先调用chunk_alloc函数从内存池分配一大块内存,该内存大小默认为20个链表节点大小,当内存池的内存也不足时,返回的内存块节点数目会不足20个。接着refill的工作就是将这一大块内存分成20份相同大小的内存块,并将各内存块连接起来形成一个链表。如下:

template <bool threads>
void *__default_alloc<threads>::refill(size_t n)
{int nobjs = __NOBJS;char *chunk = chunk_alloc(n, nobjs);  //从内存池获取内存if (nobjs == 1)  //只能分配一块,则直接返回给调用者return chunk;obj *volatile *my_free_list;obj *result, *next_obj, *current_obj;result = (obj *)chunk;my_free_list = free_list + FREELIST_INDEX(n);*my_free_list = next_obj = (obj *)(chunk + n);for (int i = 1; i < nobjs - 1; i++)  //将剩下的区块添加进链表{current_obj = next_obj;next_obj = (obj *)(char *)(next_obj + n);current_obj->free_list_link = next_obj;}//最后一块current_obj = next_obj;current_obj->free_list_link = NULL;return result;
}

内存池

chunk_alloc函数内管理了一块内存池,当refill函数要填充链表时,就会调用chunk_alloc函数,从内存池取出相应的内存。

在chunk_alloc函数内首先判断内存池大小是否足够填充一个有20个节点的链表,若内存池足够大,则直接返回20个内存节点大小的内存块给refill。如下:

        if (size_left >= total_size)  //内存池剩余空间满足需求{result = start_free;start_free += total_size;return result;}

若内存池大小无法满足20个内存节点的大小,但至少满足1个内存节点,则直接返回相应的内存节点大小的内存块给refill;

        else if (size_left >= size)  //剩余空间不能全部满足,但至少满足一块{nobjs = size_left / size;result = start_free;start_free += nobjs * size;return result;}

若内存池连1个内存节点大小的内存块都无法提供,则chunk_alloc函数会将内存池中那一点点的内存大小分配给其他合适的链表,然后去调用malloc函数分配的内存大小为所需的两倍。若malloc成功,则返回相应的内存大小给refill;若malloc失败,会先搜寻其他链表的可用的内存块,添加到内存池,然后递归调用chunk_alloc函数来分配内存,若其他链表也无内存块可用,则只能调用第一级空间配置器,因为第一级空间配置器有malloc失败的出错处理函数,最终的希望只能寄托在那里了。

如下是整个chunk_alloc函数:

template <bool threads>
char *__default_alloc<threads>::chunk_alloc(size_t size, int& nobjs)
{size_t total_size = size * nobjs;char *result;size_t size_left = end_free - start_free;if (size_left >= total_size)  //内存池剩余空间满足需求{result = start_free;start_free += total_size;return result;}else if (size_left >= size)  //剩余空间不能全部满足,但至少满足一块{nobjs = size_left / size;result = start_free;start_free += nobjs * size;return result;}else  //连一个区块都无法满足{if (size_left > 0)  //将残余内存分配给其他合适的链表{obj *volatile *my_free_list = free_list + FREELIST_INDEX(size_left);((obj *)start_free)->free_list_link = *my_free_list;  //在头部插入*my_free_list = (obj *)start_free;}size_t bytes_to_get = 2 * total_size + ROUND_UP(heap_size >> 4);start_free = (char *)malloc(bytes_to_get);if (start_free == NULL)  //堆空间不足{int i;obj *volatile *my_free_list;obj *p;for (i = size; i < __MAX_BYTES; i++){my_free_list = free_list + FREELIST_INDEX(i);p = *my_free_list;if (p != NULL){*my_free_list = p->free_list_link;start_free = (char *)p;end_free = start_free + i;return chunk_alloc(size, nobjs);}}end_free = NULL;//调用第一级配置器start_free = (char *)malloc_alloc::allocate(bytes_to_get);}heap_size += bytes_to_get;end_free = start_free + heap_size;return chunk_alloc(size, nobjs);}
}

内存释放

第二级配置器的deallocate函数并不会直接释放内存块。当内存块大小大于128字节时才会直接释放,否则会将内存块回收到相应的链表当中。如下:

void __default_alloc<threads>::deallocate(void *p, size_t n)
{//大于__MAX_BYTES,则释放该内存if (n > (size_t)__MAX_BYTES)malloc_alloc::deallocate(p, n);obj *q = (obj *)p;obj *volatile *my_free_list;my_free_list = free_list + FREELIST_INDEX(n);//小于__MAX_BYTES,则回收区块,并未释放q->free_list_link = *my_free_list;*my_free_list = q;
}

内存对外接口

STL对外提供了一个simple_alloc类,该类提供统一的接口:allocate函数、deallocate函数,使得外部无需关心使用的是几级内存配置器。另外simple_alloc类将外部所需的对象个数转换为字节。如下。

template <typename T, typename Alloc>
class simple_alloc
{
public:static T *allocate(size_t n) // 个数{return n == 0 ? 0 : (T*)Alloc::allocate(n * sizeof(T)); // 将个数转换为字节}static T *allocate(void){return (T*)Alloc::allocate(sizeof(T));}static void deallocate(T *p, size_t n) // 个数{if (n != 0)Alloc::deallocate(p, n * sizeof(T));}static void deallocate(T *p){Alloc::deallocate(p, sizeof(T));}
};

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

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

相关文章

【自然语言处理】BitNet b1.58:1bit LLM时代

论文地址&#xff1a;https://arxiv.org/pdf/2402.17764.pdf 相关博客 【自然语言处理】BitNet b1.58&#xff1a;1bit LLM时代 【自然语言处理】【长文本处理】RMT&#xff1a;能处理长度超过一百万token的Transformer 【自然语言处理】【大模型】MPT模型结构源码解析(单机版)…

如何在 Mac 上成功轻松地恢复 Excel 文件

Microsoft Excel 的 Mac 版本始终略落后于 Windows 版本&#xff0c;这也许可以解释为什么如此多的用户渴望学习如何在 Mac 上恢复 Excel 文件。 但导致重要电子表格不可用的不仅仅是 Mac 版 Excel 的不完全稳定性。用户有时会失去注意力并删除错误的文件&#xff0c;存储设备…

2024-03-03 c++

&#x1f338; MFC进度条控件 | Progress Control 1。新建MFC项目&#xff08;基于对话框、静态库&#xff09; 2。添加控件&#xff0c;删除初始的3个多余控件 加1个progress control&#xff0c;修改其marquee为true&#xff0c;添加变量&#xff1a;变量名为test_progress。…

Angular基础---HelloWorld---Day1

文章目录 1. 创建Angular 项目2.对Angular架构的最基本了解3.创建并引用新的组件&#xff08;component&#xff09;4.对Angular架构新的认识&#xff08;多组件&#xff09;5.组件中业务逻辑文件的编辑&#xff08;ts文件&#xff09;6.标签中属性的绑定(1) ID的绑定(2) class…

String和String Builder

String和StringBuilder的区别 String类 String类代表字符串。java程序中所有字符串文字&#xff08;例如“abc”&#xff09;都被实现为此类的实例。 String类源码是用final修饰的&#xff0c;它们的值在创建后不能被更改。字符串缓冲区支持可变字符串。 String对象是不可变…

STM32 (2)

1.stm32编程模型 将C语言程序烧录到芯片中会存储在单片机的flsah存储器中&#xff0c;给芯片上电后&#xff0c;Flash中的程序会逐条进入到CPU中去执行&#xff0c;进而CPU去控制各种模块&#xff08;即外设&#xff09;去实现各种功能。 2.寄存器和寄存器编程 CPU通过控制其…

Apache POI的简单介绍与应用

介绍 Apache POI 是一个处理Miscrosoft Office各种文件格式的开源项目。我们可以使用 POI 在 Java 程序中对Miscrosoft Office各种文件进行读写操作。PS&#xff1a; 一般情况下&#xff0c;POI 都是用于操作 Excel 文件&#xff0c;如图&#xff1a; Apache POI 的应用场景&…

SQL无列名注入

SQL无列名注入 ​ 前段时间&#xff0c;队里某位大佬发了一个关于sql注入无列名的文章&#xff0c;感觉好像很有用&#xff0c;特地研究下。 关于 information_schema 数据库&#xff1a; ​ 对于这一个库&#xff0c;我所知晓的内容并不多&#xff0c;并且之前总结SQL注入的…

设计模式-桥接模式实践案例

桥接模式&#xff08;Bridge Pattern&#xff09;是一种结构型设计模式&#xff0c;用于将抽象与实现分离&#xff0c;使它们可以独立地变化。这种模式通过提供一个桥接结构&#xff0c;可以将实现接口的实现部分和抽象层中可变化的部分分离开来。 以下是一个使用 Java 实现桥…

【数据结构】_包装类与泛型

目录 1. 包装类 1.1 基本数据类型和对应的包装类 1.2 &#xff08;自动&#xff09;装箱和&#xff08;自动&#xff09;拆箱 1.2.1 装箱与拆箱 1.2.2 自动&#xff08;显式&#xff09;装箱与自动&#xff08;显式&#xff09;拆箱 1.3 valueOf()方法 2. 泛型类 2.1 泛…

【深度学习笔记】计算机视觉——目标检测和边界框

目标检测和边界框 前面的章节&#xff08;例如 sec_alexnet— sec_googlenet&#xff09;介绍了各种图像分类模型。 在图像分类任务中&#xff0c;我们假设图像中只有一个主要物体对象&#xff0c;我们只关注如何识别其类别。 然而&#xff0c;很多时候图像里有多个我们感兴趣…

某大型制造企业数字化转型规划方案(附下载)

目录 一、项目背景和目标 二、业务现状 1. 总体应用现状 2. 各模块业务问题 2.1 设计 2.2 仿真 2.3 制造 2.4 服务 2.5 管理 三、业务需求及预期效果 1. 总体业务需求 2. 各模块业务需求 2.1 设计 2.2 仿真 2.3 制造 2.4 服务 2.5 管理 四、…

在vue中对keep-alive的理解,它是如何实现的,具体缓存的是什么?

对keep-alive的理解&#xff0c;它是如何实现的&#xff0c;具体缓存的是什么&#xff1f; &#xff08;1&#xff09;keep-alive有以下三个属性&#xff1a;注意&#xff1a;keep-alive 包裹动态组件时&#xff0c;会缓存不活动的组件实例。主要流程 &#xff08;2&#xff09…

数字化转型导师坚鹏:证券公司数字化营销

证券公司数字化营销 ——借力数字化技术实现零售业务的批量化、精准化、场景化、智能化营销 课程背景&#xff1a; 很多证券公司存在以下问题&#xff1a; 不知道如何提升证券公司数字化营销能力&#xff1f; 不知道证券公司如何开展数字化营销工作&#xff1f; 不知道…

胎神游戏集第二期

延续上一期 一、海岛奇胎 #include<bits/stdc.h> #include<windows.h> #include<stdio.h> #include<conio.h> #include<time.h> using namespace std; typedef BOOL (WINAPI *PROCSETCONSOLEFONT)(HANDLE, DWORD); PROCSETCONSOLEFONT SetCons…

Linux 安装pip和换源

一 配置文档 Linux和macOS&#xff1a; 全局配置&#xff1a;/etc/pip.conf 用户级配置&#xff1a;~/.pip/pip.conf 或 ~/.config/pip/pip.conf 二 下载 和 安装 # pip 安装 wget https://bootstrap.pypa.io/get-pip.py python get-pip.py 三 查看和升级 pip -Vpython -m…

GO语言学习笔记(与Java的比较学习)(十一)

协程与通道 什么是协程 一个应用程序是运行在机器上的一个进程&#xff1b;进程是一个运行在自己内存地址空间里的独立执行体。一个进程由一个或多个操作系统线程组成&#xff0c;这些线程其实是共享同一个内存地址空间的一起工作的执行体。 并行是一种通过使用多处理器以提…

Java虚拟机 - JVM

JVM的内存区域划分 JVM它其实也是一个进程,进程运行的过程中,会从操作系统中申请一些资源.内存就是其中的一种.这些内存就支撑了java程序的运行.JVM从系统中申请的一大块内存,会根据实际情况和使用用途来划分出不同的空间,这个就是区域划分.它一般分为 堆区, 栈区, 程序计数器…

springboot240基于Spring boot的名城小区物业管理系统

基于Spring boot的名城小区物业管理系统的设计与实现 摘要 当下&#xff0c;正处于信息化的时代&#xff0c;许多行业顺应时代的变化&#xff0c;结合使用计算机技术向数字化、信息化建设迈进。以前相关行业对于物业信息的管理和控制&#xff0c;采用人工登记的方式保存相关数…

InnoDB存储引擎对MVCC的实现

MVCC MVCC的目的 在搞清楚MVCC之前,我们要搞懂一个问题,MVCC到底解决的是什么问题? 我用一句话概括,那就是为了解决读-写可以一起的问题! 在我们的印象里,InnoDB可以读读并发,不能读写并发,或者写写并发 这是很正常的想法,因为如果读写并发的化,会有并发问题 而对于写写…