目录

本文只总结了空间配置器的一些考点作为初学者了解用,具体实现细节请参考文末附加的链接。
1.空间配置器
-
what
空间配置器,顾名思义,就是为各个容器高效管理空间(空间的申请与回收)的,在默默地工作。
虽然在常规使用STL时,可能用不到它,但站在学习研究的角度,学习它的实现原理对我们有很大的帮助。
-
why
当我们自己动手实现vector、list、map、unordered_map等容器时,所有需要的内存空间都是通过malloc或new申请的,虽然代码可以正常运行,但是有以下不足之处:
- 内存泄漏:堆区空间申请与释放需要程序员自己管理;
- 内存碎片:频繁申请小块内存会产生外部碎片(注意区分内部碎片与外部碎片);
- 效率低:频繁申请小块内存;
- 代码结构混乱、复用性不高;
因此,需要设计一个高效的内存管理机制,在STL中该模块被称之为空间配置器。
-
how
SGI-STL(硅图公司)空间配置器的原理:
通常来讲,C++内存分配与释放使用new和delete。
对于new,是先分配内存,后调用构造函数;而对于delete,是先调用析构函数,后释放内存。
SGI-STL空间配置器将这一过程设计为四个操作:内存配置(alloc::allocate)、内存释放(alloc::deallocate)、对象构造(alloc::construct)、内存释放(alloc::destroy)。
本文主要总结内存配置与释放的模块。
SGI-STL空间配置器分为标准空间配置器(std::allocator)以及特殊空间配置器(std::alloc)。
标准空间配置器(std::allocator)只是对new和delete的浅层封装,实用价值较低,在SGI-STL中也从未使用过。
通常使用的是特殊空间配置器(std::alloc),该空间配置器采用两级结构。
2.特殊空间配置器(std::alloc)
-
what
SGI-STL以128字节作为小块内存与大块内存的分界线,设计了具有两级结构的特殊空间配置器。
-
why
上述提到的不足之处,究其原因是由于频繁使用malloc和free向系统申请小块内存造成的,因此设计具有两级结构的特殊空间配置器来解决上述问题。
-
how
>128字节的内存使用一级空间配置器,<=128字节的内存使用二级空间配置器。
3.一级空间配置器
-
what
用于分配>128字节内存大小的空间。
-
why
为了更大程度合理运用空间,压榨剩余内存,达到内存的高效使用,尽量开辟想要的内存空间(在原理中会讲到为什么能压榨剩余内存)。
-
how
直接调用malloc和free(而不是new和delete,前文提到了,在空间配置器中分别具有内存配置、内存释放、对象构造、内存释放四个操作)。
该设计的两个关键之处:在Oom_Malloc中不断循环申请空间(上文提到的压榨剩余内存的原因)、new_handler机制
先看一眼整体的设计逻辑:
一级空间配置器通过malloc()申请空间,而释放空间直接调用free(),当内存不足申请失败时,调用函数Oom_Malloc(Oom=Out of memory)
在Oom_Malloc中,模拟C++的new_handler机制,通过一个函数指针指向指定的处理函数
所谓C++ new handler 机制就是:
你可以要求系统在内存配置需求无法被满足的时候,调用一个你指定的函数,
也就是说,一旦 ::operator new 无法完成任务,在丢出异常之前,会先调用客户端指定的处理函数,该处理函数通常被称为 new-hanler。
如果不存在自定义该处理函数,则抛出异常,通过exit(1)的方式退出;
如果存在自定义的处理函数,则调用该处理函数申请内存空间。
Oom_Malloc函数只有两个出口点:1.不存在自定义处理函数,抛出异常;2.申请到空间,返回并退出。
可以看出,在定义了指定的处理函数的情况下,如果没有申请到对应大小的内存,则会陷入死循环,等待有对应大小的内存直到申请成功。
这一设计使得内存能够得到高效的使用。
以下是截取的部分关键代码:
#pragma once
#include<cstdlib> //malloc、free的头文件
#include<iostream>
/*__malloc_alloc_template*/
typedef __malloc_alloc_template<0> malloc_alloc;
typedef malloc_alloc alloc;
//定义的抛出异常的宏,通过exit(1)的方式退出
#define __THROW_BAD_ALLOC std::cerr<<"out of memory"<< std::endl; exit(1)
class OneSpce
{
private:
//以下是函数指针,所代表的函数将用来处理内存不足的情况
//oom(out of memory)
static void *Oom_Malloc(size_t);
static void *Oom_Realloc(void *, size_t);
static void(*__malloc_alloc_oom_handler)();
public:
// 对malloc的封装
static void * Allocate(size_t n)
{
//一级空间配置器直接使用malloc(),申请空间成功,直接返回,失败则交由oom_malloc函数处理
void *result = malloc(n);
//内存空间申请失败时,调用 oom_malloc()
if (0 == result)
result = Oom_Malloc(n);
return result;
}
// 对free的封装 这个size_t完全可以不要,但是为了统一接口,加上了这个size_t
static void Deallocate(void *p, size_t /* n */)
{
//一级空间配置器直接使用free()
free(p);
}
// 模拟set_new_handle
// 该函数的参数为函数指针,返回值类型也为函数指针
// void (* set_malloc_handler( void (*f)() ) )()
typedef void(*PFUNC)();
static PFUNC set_malloc_handler(PFUNC f)
static void(*set_malloc_handler(void(*f)()))()
{
void(*old)() = __malloc_alloc_oom_handler;
__malloc_alloc_oom_handler = f;
return(old);
}
};
//如果没有设置的话,在下面的函数中就只能抛出异常
void(*OneSpce::__malloc_alloc_oom_handler)() = 0;
// malloc申请空间失败时改用该函数
void * OneSpce::Oom_Malloc(size_t n)
{
void(*my_malloc_handler)();
void *result;
//不断的尝试释放、配置、再释放、在配置
for (;;)
{
// 检测用户是否设置空间不足应对措施,如果没有设置,抛出异常
my_malloc_handler = __malloc_alloc_oom_handler;
if (0 == my_malloc_handler)
{
__THROW_BAD_ALLOC;
}
// 如果设置,执行用户提供的空间不足应对措施
(*my_malloc_handler)();
// 继续申请空间,可能就会申请成功
result = malloc(n);
if (result)
return(result);
}
}
4.二级空间配置器
-
what
用于分配<=128字节内存大小的空间。
-
why
为了避免频繁申请小块内存的操作造成的效率低、内存碎片的问题,并提高用户获取空间的速度与高效管理,减小外部碎片的生成。
-
how
内存池+自由链表的设计。
通过预先申请大块内存作为内存池,随后将内存池中的内存分块挂载到自由链表上,需要内存从自由链表上取,不再使用的内存放回内存池重新挂载到自由链表上,并未释放。
5.内存池
-
what
顾名思义,它是预先申请的一大块内存,具有三个参数:内存起始地址、终止地址以及内存池大小(=终止地址-起始地址)。
-
why
为了避免频繁申请小块内存的操作造成的效率低、内存碎片的问题。
-
how
第一次申请小内存时,先申请一大块内存留作备用,之后申请小内存时,直接从已经申请的一大块内存中划去,不再向系统申请,当内存池空间不足时,再从堆区申请空间。
6.自由链表
-
what
实际上是哈希桶结构(拉链法解决哈希冲突)。
-
why
为了提高用户获取空间的速度与高效管理,减小外部碎片的生成。
-
how
将内存池中内存划分为8的倍数大小的区块,分别为8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128字节共16个大小类型,挂接到free_list中,
并主动将任何小额区块的内存需求量上调到8的整数倍(如申请25字节的内存,则分配32字节内存),
若有相同大小的内存需求,直接从free_list中取,如果客户端释放小额区块,就由配置器回收到 free_lists 中。
7.总结
申请一块n字节的内存的逻辑:
n>128 调用一级空间配置器,n<=128 调用二级空间配置器。
附逻辑图:
空间配置器解决的问题:
1.通过内存池解决了频繁使用malloc、free开辟和释放小内存带来的性能效率低下;
2.通过free_list设计分配8字节倍数大小内存优化外部碎片问题,但并没有完全解决内存碎片的问题(只能优化,并不能完全不产生碎片)。
带来的问题:
1.没有释放自由链表free_list所挂区块的函数,释放时机是程序结束,导致自由链表一直占用内存,自己进程可以使用,其他进程却用不了。
附参考链接(含部分源码):
https://blog.csdn.net/qq_40421919/article/details/89192383