stl有容器,空间配置器,适配器,迭代器,仿函数以及算法这6个组件,它们六者关系大概如下:容器通过配置器取得数据存储空间,算法通过迭代器获取容器内容,仿函数可以协助算法完成不同的策略变化,配接器可以修饰或套界仿函数。
空间配置器
我们知道STL容器在不断保存数据时,当保存的的数据个数超过容器容量时,需要进行扩容。但是,当不断保存数据时,就可能需要不断的进行扩容。此时,扩容需要不断的向操作系统申请空间,释放空间。操作系统是很繁忙的,这样会大大影响操作系统的效率。
RedisSubCommand 函数的第二个参数是一个回调函数,因此通过“PFunCallBack pFunCallback”(即,类型+变量)的形式,给出对应回调函数(pFunCallback)的地址。
于是就出现了空间配置器。
1 概念
空间配置器:是操作系统开辟的一大段内存空间。STL需要扩容申请内存时,就从空间配置器中申请,不需要再经过操作系统。并且,它还能回收释放的空间,供下一次使用。
空间配置器原理
空间配置器有两级结构,一级空间配置器是用来处理大块内存,二级空间配置器处理小块内存。SGI-STL规定以128字节作为小块内存和大块内存的分界线。
为什么这样区分成两级?
因为STL容器,一般申请的都会是小块的内存。
二级空间配置器,主要是管理容器申请空间和释放的空间。
如果用户申请的空间直接大于的128字节直接找的是一级空间配置器申请空间。
2 一级空间配置器
一级空间配置器原理很简单,直接是对malloc和free进行了封装,并且增加了C++中的set_new_handle思想,即申请空间失败抛异常机制。
主要的作用是:向操作系统申请内存,申请失败会抛异常。
为什么不直接用C++的new和delete,因为这里并不需要调用构造函数和析构函数。
用户可以自定义申请空间失败的措施,如果没有设置,就会抛异常。
set_new_handler(0)
inline T* _allocate(ptrdiff_t size, T*)
{
std::set_new_handler(0);
T* tmp = (T*)(::operator new((size_t)(size * sizeof(T))));
if (tmp == 0)
{
std::cerr << "out of menory" << std::endl;
}
return tmp;
}
inline T* _allocate(ptrdiff_t size, T*)
{
std::set_new_handler(0);
T* tmp = (T*)(::operator new((size_t)(size * sizeof(T))));
if (tmp == 0)
{
std::cerr << "out of menory" << std::endl;
}
return tmp;
}
解释
首先说一下C++对内存分配的原理。如果程序员决定用new operator向计算机申请一块内存,那么就可能会遇到内存不够的情况。一旦内存不够申请失败,那么默认情况下C++会抛出std::bad_alloc异常。但是如果你不想让它抛出异常,而是想自己写一个程序来处理内存不够的情况,那么你就可以用set_new_handler(new_handler),把new_handler指向你写的内存不够的处理程序。这样内存不够了的话C++就会去调用你写的内存不够处理程序,然后再做后续处理。如果你写set_new_handler(0)也就是set_new_handler(nullptr),实际上就是强制C++认为你没有自定义的内存不够处理程序(因为指针是0嘛),为了强制C++在内存不够的时候抛出std:bad_alloc,而不是去执行什么其他自定义的内存不够处理程序。
template<class T, class Alloc = AllocToUse>
class SimpleAlloc
{
public:
static T* Allocate()
{
return (T*)Alloc::Allocate(sizeof(T));
}
static T* Allocate(size_t n)
{
return n == 0 ? 0 : (T*)Alloc::Allocate(n * sizeof(T));
}
static void Deallocate(T* p)
{
if (p != NULL)
return Alloc::Deallocate(p, sizeof(T));
}
static void Deallocate(T* p, size_t n)
{
return Alloc::Deallocate(p, n * sizeof(T));
}
};
一级空间配置器的allocate()如果调用malloc()分配内存不成功,将会调用内存不足处理函数oom_malloc(),oom_malloc()会循环调用错误处理函数__malloc_alloc_oom_handler,企图释放内存,再重新调用malloc(),直到分配成功;如果未设定错误处理函数,将直接bad_alloc异常信息
template<int Inst>
class __MallocAllocTemplate //一级空间配置器
{
typedef void (*OOM_HANDLER)();
private:
//these funs below are used for "OOM" situations
//OOM = out of memory
static void* OOM_Malloc(size_t n); //function
static void* OOM_Realloc(void *p, size_t newSZ); //function
static OOM_HANDLER OOM_Handler; //function pointer
public:
static void* Allocate(size_t n)
{
void* ret = malloc(n);
if (ret == NULL)
ret = OOM_Malloc(n); //如果失败的话调用内存不足处理函数
return ret; //先申请内存,成功的话直接返回
}
static void Deallocate(void* p, size_t n)
{
free(p);
}
static void* Reallocate(void* p, size_t oldSZ, size_t newSZ)
{
void* ret = realloc(p, newSZ);
if (ret == NULL)
ret = OOM_Realloc(p, newSZ);
return ret;
}
//static void (* set_malloc_handler(void (*f)()))() //设置错误处理函数
//参数和返回值都是函数指针void (*)()
static OOM_HANDLER SetMallocHandler(OOM_HANDLER f)
{
OOM_HANDLER old = OOM_Handler;
OOM_Handler = f;
return old;
}
/*
// 模拟set_new_handle
// 该函数的参数为函数指针,返回值类型也为函数指针
static void(*set_malloc_handler(void(*f)()))()
{
void(*old)() = __malloc_alloc_oom_handler;
__malloc_alloc_oom_handler = f;
return(old);
}
*/
};
//让函数指针为空
template<int Inst>
void (*__MallocAllocTemplate<Inst>::OOM_Handler)() = NULL;
template<int Inst>
void* __MallocAllocTemplate<Inst>::OOM_Malloc(size_t n)
{
void* ret = NULL;
void(*myHandler)() = NULL;
for (;;) //循环调用错误处理函数
{
\\// 检测用户是否设置空间不足应对措施,如果没有设置,抛异常,模式new的方式
myHandler = OOM_Handler;
if (myHandler == NULL)
throw bad_alloc(); //抛异常
(*myHandler)(); //如果设置了,执行用户提供的空间不足应对措施
\\继续申请空间,可能就会申请成功
ret = malloc(n);
if (ret != NULL)
return ret;
}
}
template<int Inst>
void* __MallocAllocTemplate<Inst>::OOM_Realloc(void* p, size_t newSZ)
{
void* ret = NULL;
void(*myHandler)() = NULL;
for (;;)
{
myHandler = OOM_Handler;
if (myHandler == NULL)
throw bad_alloc();
(*myHandler)();
ret = realloc(p, newSZ);
if (ret != NULL)
return ret;
}
}
typedef __MallocAllocTemplate<0> MallocAlloc; //一级空间配置重命名
3 二级空间配置器
二级空间配置器专门负责处理小于128字节的小块内存。
SGI-STL采用了内存池的技术来提高申请空间的速度以及减少额外空间的浪费,采用哈希桶的方式来提高用户获取空间的速度和高效管理。
内存池技术
内存池就是,先申请一块较大的内存块做为备用,当需要内存时,直接从内存池中取内存,当内存池中内存不够时,使用一级空间配置器,向内存中申请大块空间。当用户不用申请的空间时,直接归还内存池。这样就避免了频繁向系统申请小块内存找出的效率低,内存碎片的问题。
但是这样会有一个问题:如何归还?
用户申请空间很简单,直接从内存池中申请。但是,当用户释放该空间时,并不知道这块空间应该放在内存池的什么位置。归还成了一个问题。
在STL配置器中,使用哈希桶的技术来解决这一问题。
哈希桶技术
为什么不用链表来管理归还的空间?
因为用户申请空间,查找合适的内存块时,效率低。
为什么向上对齐到了8字节的整数倍?
因为在哈希桶下,是以链表的形式将所有内存块连接起来的。
该内存块至少需要保存下一个内存块的地址,在64位系统下,为8字节。
但是这样造成了内存碎片问题。
大致流程:
容器进行扩容,如果申请的空间是大于128字节,直接向一级空间配置器申请。如果小于128字节,先查找哈希桶对应大小位置是否为空,不为空,直接从该位置申请空间,如果该位置为空,向内存池申请。当内存池空间不够了会直接向OS申请一大块空间。
- 空间配置器:https://blog.csdn.net/weixin_57023347/article/details/121207344
https://blog.csdn.net/xy913741894/article/details/66974004
https://www.cnblogs.com/Joezzz/p/10300512.html
https://www.jianshu.com/p/3225a0851382