C++附加篇: 空间适配器

空间适配器是STL中负责高效内存管理的组件,通过一级和二级适配器减少内存碎片和提高性能。一级适配器是对malloc/free的封装,二级适配器采用哈希桶管理小块内存,利用内存对齐减少所需桶的数量。适配器在多线程环境中确保线程安全,并帮助解决内存泄漏和碎片问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 "我有时难过,却还有些抚慰和感动。"

一、我们来谈谈空间适配器        

(1) 什么是空间配置器?

        STL的六大组件,容器、算法、迭代器、适配器、仿函数,最后一个也就是"空间适配器"。

        所谓"空间适配器",顾名思义,就是对STL中各个容器的内存进行高效的管理。也许你会说,诶,我写了这么多的C++代码,为什么没有这个概念呢?或者说,为什么我们根本没有见到!这个空间适配器呢?

        然而事实上,不是说,我们没有使用,而是在我们使用诸如vector\list\deque时,我们的空间配置器是在默默地为我们进行工作。

(2) 为什么需要空间适配器呢?

        我们使用堆上的空间,管它三七二十一,直接malloc 或者new不就得了?为什么还需要在空间申请的过程里,添加这一个适配器呢?对于使用者而言不麻烦吗?对于设计者而言,不也会给他们带来麻烦嘛?

        是的,如果我们仅单单从学习语言的角度来看,似乎无脑用malloc、new并没有啥有待商榷的地方,毕竟那本来就是提供给应用层调用的函数。

        但如果站在系统层面上,也许你在语言层调用malloc、new只是看到了单单的函数调用,并你接收到了来自函数的返回值"void*",你就可以针对这一块对空间上的内存块进行操作,仅此而已。你根本不知道操作系统在底层为你的行为做了哪些操作。

 唔,大概在底层,操作系统会为你做如下的事情:

        如果是在Windos下,malloc\new在底层会去调用 VirtualAlloc 向操作系统申请堆空间,如果是在Linux下,malloc、new会在底层调用 brk或者 mmap得到堆空间的起始地址。

        这似乎很符合我们的预期,与maloc、new相比,不就多调用了一次函数而已? 但事实真的是这样嘛?

        我们以在Linux环境下申请、释放空间例举:

我们以ARM64架构下来划分虚拟进程地址空间,编制从全0~全F。

高16位(0xFFFF 0000 0000 0000 ~ 0xFFFF FFFF FFFF FFFF) -->  内核地址空间

低16位(0x0000 0000 0000 0000 ~ 0x0000 FFFF FFFF FFFF) --> 用户地址空间

        不管我们使用什么样的函数,一旦涉及到要使用系统资源,例如: 堆空间、文件描述符、套接字描述符…… 其底层都需要访问系统提供的接口函数。然而,用户是不能直接执行内核代码的,而是需要切换成 内核用户才能执行代码,这个过程也叫做 "陷入内核",将用户态切换为内核态。显然,这个过程是很耗时的。

        不仅如此,Linux有自己内部的内存管理系统,如"伙伴系统",它需不需要维护系统堆空间上的资源?需要!难道它直接就把那块内存块扔给 用户?需不需要调剩余内存块的结构呢? 它需不需要对释放完的内存块进行管理,以避免内存碎片问题……

        有了上面的论述,空间适配器的出现,也就具有必然性。

malloc\new的不足之处

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

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

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

④ 未考虑线程安全问题。

⑤ 代码结构比较混乱,代码复用率不高。

        因此需要设计一块高效的内存管理机制。


二、空间配置器窥探源码

(1) 空间适配器原理

        以上提及的用new、malloc最主要的一个问题是,"频繁"二字,在SGI版本中,空间配置器
以128作为 小块内存 与 大块内存的分割线。由此,其空间分配的结构分为两个的等级:一级空间配置器用于处理 大块内存的申请、释放,二级空间配置器用于处理 小块内存的申请、释放。

(2) 具体实现

一级适配器:

        一级适配器原理很简单,就是一个对malloc、free简单的封装。

         例如这里一个simple_alloc 使用这个适配器。

二级适配器:

        我们在前面说SGI版本的Alloc,对于二级适配器而言,是一个管理这1~128这个范围的内存块。那么如何管理这一堆切小的小块内存呢,SGI采用了哈希桶的方式进行管理。但是,1~128,难道需要我们用开128个桶的空间来管理嘛?答案是否定的,第一个是从使用上来说,大多数开辟空间的大小都是4的正数倍,其次是,如果对内存空间的管理过于细腻,必定会造成一定空间浪费的问题。因此,SGI-STL将用户申请的内存块,向上对齐按照8byte。

         此时,我们原本需要128个桶来唯一标识一个内存块对应的挂接位置,变为只需要16个桶。

  // 计算对齐后的 大小
  static size_t ROUND_UP(size_t bytes) {
        return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));
  }

    
  // 计算该字节大小 对应哪个桶
  static  size_t FREELIST_INDEX(size_t bytes) {
        return (((bytes) + __ALIGN-1)/__ALIGN - 1);
  }

        STI-STL提供了两个函数,分别可用来计算 对齐字节大小 和 内存块挂接的桶位置。

refill与chunk_alloc: 

        二级适配器还考虑了多线程环境下,Alloc的场景。 

(3) 空间适配器与具体容器


三、 如何理解STL?

        STL的六大组件包括,算法、迭代器、容器、适配器、分配器(空间配置器)、仿函数。这几者有何关联呢?


总结:

        空间配置器其底层技术就是采用的池化技术,可以说就是一个小型的内存池。能在频繁申请小块内存的场景中,提高一定的性能。

        STL六大组件:算法、迭代器、容器、适配器、分配器(空间配置器)、仿函数。

本篇到此结束,感谢你的阅读

祝你好运,向阳而生~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值