一、STL介绍
STL(Standard Template Library,标准模板库),从根本上说,STL是一些“容器”的集合,这些“容器”有list,vector,set,map等,STL也是算法和其他一些组件的集合。
首先呢,让我们一起来理解一下STL的六大组件:
1、STL六大组件简单介绍
(1)容器(Container):作为STL的最主要组成部分--容器,分为序列式容器和关联式容器:
序列式容器主要包括:向量(vector),双端队列(deque),表(list),队列(queue),堆栈(stack)
关联式容器主要包括:集合(set),多重集合(multiset),映射(map),多重映射(multimap)。
(2)算法(Algorithm):算法部分主要由头文件,和组成;STL的算法也是非常优秀的,它们大部分都是类属的,基本上都用到了C++的模板来实现,这样,很多相似的函数就不用自己写了,只要用函数模板就可以了。
(3)迭代器(Iterator):扮演容器与算法之间的胶合剂,是所谓的“泛型指针”,共有五种类型,以及其它衍生变化,从实现的角度来看,迭代器是一种将:Operators*,Operator->,Operator++,Operator–等相关操作予以重载的Class Template。所有STL容器都附带有自己专属的迭代器——是的,只有容器设计者才知道如何遍历自己的元素,原生指针(Native pointer)也是一种迭代器。
(4)仿函数(Functors): 行为类似函数,可作为算法的某种策略(Policy),从实现的角度来看,仿函数是一种重载了Operator()的Class 或 Class Template。一般函数指针可视为狭义的仿函数。
(5)配接器(适配器)(Adapters):一种用来修饰容器(Containers)或仿函数(Functors)或迭代器(Iterators)接口的东西,例如:STL提供的Queue和Stack,虽然看似容器,其实只能算是一种容器配接器,因为 它们的底部完全借助Deque,所有操作有底层的Deque供应。
(6)配置器(Allocators):负责空间配置与管理,从实现的角度来看,配置器是一个实现了动态空间配置、空间管理、空间释放的Class Template。
二、空间配置器
这篇博客主要是讲解STL中的六大组件之一—–空间配置器,接下来就是重点内容介绍了。
1、什么是空间配置器?
所谓空间配置器就是用来管理内存的一个器具,对于STL来说,空间适配器是它可以正常工作的基础,也为它可以高效工作提供了动力。对于使用STL来说,它是不和用户直接打交道的,而是隐藏在一切STL组建之后,默默为各种内存申请提供支持的。
2、为什么有适配器?
1、小块内存带来的内存碎片问题
单从分配的角度来看。由于频繁分配、释放小块内存容易在堆中造成外碎片(极端情况下就是堆中空闲的内存总量满足一个请求,但是这些空闲的块都不连续,导致任何一个单独的空闲的块都无法满足这个请求)。
2、小块内存频繁申请释放带来的性能问题。
开辟空间的时候,分配器会去找一块空闲块给用户,找空闲块也是需要时间的,尤其是在外碎片比较多的情况下。如果分配器其找不到,就要考虑处理假碎片现象(释放的小块空间没有合并),这时候就要将这些已经释放的的空闲块进行合并,这也是需要时间的。
3、小块空间太多会造成空间的浪费
每一次malloc 函数开辟出一块堆空间 在返回的指针前几个字节保存了开辟空间的大小。 这样free()的时候才知道传进去的指针到底意味着的多大的空间。 所以多开辟的空间叫配置空间。小空间向系统申请过多,这些记录空间大小的内存就太多造成内存浪费。
4、malloc new 出来的空间要 free delete 释放 如果忘记不释放会发生内存泄漏。
3、内存池
为了解决上面的内存碎片问题,就提出了内存池的概念
内存池最基本的思想
内存池最基本的思想就是一次向heap申请一块很大的内存(内存池),如果申请小块内存的话就直接到内存池中去要。这样的话,就能够有效的解决上面所提到的问题。
4、空间配置器的分类
STL里面的空间配置主要分为两级:一级空间配置器(__malloc_alloc_template)和二级空间配置器(__default_alloc_template)。
(1)在STL中默认如果要分配的内存大于128个字节的话就是大块内存,调用一级空间配置器直接向系统申请;
(2)如果小于等于128个字节的话则认为是小内存,则就去内存池中申请。
5、new的底层实现
谈及new和delete,我们一定不会太陌生,那么它的底层实现是怎样的呢,具体有以下几个步骤:
在C++中的new 内含的操作 :
(1)简单类型直接调用operator new分配内存;
(2)可以通过new_handler来处理new失败的情况;
(3)new分配失败的时候不像malloc那样返回NULL,它直接抛出异常。要判断是否分配成功应该用异常捕获的机制;
(4)new 复杂数据类型的时候先调用operator new,然后在分配的内存上调用构造函数。
在C++中的delete的操作 :
(1)delete简单数据类型默认只是调用free函数。
(2)delete复杂数据类型先调用析构函数再调用operator delete。
三、一级空间配置器
一级空间配置器的实现相对比较简单,直接封装了malloc()和free()函数,同时增加了处理机制et_malloc_handler() 。
一级空间配置器的执行流程图:
模拟实现一级空间配置器:
#include<iostream>
using namespace std;
typedef void(*pMallocHandler)();
template<int inst>
class MallocAllocTemplate
{
public:
static void* Allocate(size_t n)//开辟空间
{ //调用malloc函数开辟空间
void *ret = malloc(n);
//如果申请失败就调用OOM_Malloc()函数
if (NULL == ret)
ret = OOM_Malloc(n);
return ret;
}
static void Deallocate(void *p)//释放空间
{
free(p);//内部直接调用free函数即可
}
static void *RealAllocate(void* p, size_t, size_t newSize)//在原空间的基础上开辟空间
{
//内部直接调用realloc()函数
void *ret = realloc(p, newSize);
if (NULL == ret)//若空间开辟失败则调用OOM_Realloc函数
ret = OOM_Realloc(p.newSize);
return ret;
}
private:
//处理分配内存时空间不足的情况
static void *OOM_Malloc(size_t size)
{
pMallocHandler mallochandler;
void* ret = NULL;
for (;;)//不断的尝试释放和配置是因为用户不知道还需要释放多少内存来满足分配需求,只能逐步的释放配置
{
mallochandler = _MallocHandler;//用户自定义处理函数
//因为内存不足处理函数初值设置的是NULL,如果用户没有自定义,就直接抛出异常
if (NULL == mallochandler)
throw std::bad_alloc();
//若定义了,则调用用户自定义处理函数,释放已经获取但是不用的堆内存
mallochandler();
//重新分配内存
ret = malloc(size);
if (ret)
return ret;
}
}
static void *OOM_Realloc(void*, size_t size)
{
pMallocHandler mallochandler;
void* ret;
for (;;)
{
mallochandler = _MallocHandler;
if (NULL == mallochandler)
throw std::bad_alloc();
//释放已经获取但不用的堆空间
mallochandler();
ret = realloc(p, size);
if (ret)
return ret;
}
}
//用户自己定义内存不足情况下的处理函数
//该函数接收一个返回值为空,参数为空的函数指针作为参数,最后返回一个返回值和参数均为空的函数指针
static pMallocHandler setMallocHander(pMallocHandler mallochandler)
{
pMallocHandler old = _MallocHandler;//保存旧的处理例程
_MallocHandler = mallochandler;//重新设置新的处理例程
return old;
}
private:
static pMallocHandler _MallocHandler;
};
//内存不足处理函数初值设置为0,等用户自己定义
template<int inst>
pMallocHandle MallocAllocTemplate<inst>::_MallocHandler = NULL;
一级空间配置器源码
template<int inst>
class _malloc_alloc_template
{
/* oom_alloc为静态函数成员,用于处理malloc时的内存不足问题
oom_realloc为静态函数成员,用于处理realloc时的内存不足问题
_malloc_alloc_handler为静态数据成员,为void(*)()类型的函数指针,用于用户自
己制定内存分配策略
*/
static void * oom_malloc(size_t);//out_of_memmory malloc
static void * oom_realloc(void *, size_t);
static void(*_malloc_alloc_oom_handler)();
public:
static void * allocate(size_t n)
{
void * result = malloc(n);//请求内存
if (result == nullptr)//如果内存不足
result=oom_malloc(n);//调用oom_malloc
return result;
}
static void * reallocate(void * p, size_t n)
{
void *result = realloc(n);
if (result == nullptr)
result = oom_realloc(p, n);
return result;
}
static void deallocate(void * p)
{
//使用free函数释放p地址后所分配的内存块
free(p);
}
/*此静态成员函数接受一个void(*)()类型的函数指针作为参数,返回
void(*)()类型的函数指针。其作用为用用户自己定制的内存调度方法替换
_malloc_alloc_handler,由此实现类似C++的set_new_handler方法。
*/
static void(* set_malloc_handler(void(*f)()))()
{
void(*old)() = _malloc_alloc_oom_handler;
_malloc_alloc_oom_handler = f;
return old;
}
};
template<int inst>
void(*_malloc_alloc_template<inst>::_malloc_alloc_oom_handler)() = 0;
template<int inst>
void * _malloc_alloc_template<inst>::oom_malloc(size_t n)
{
void(*my_oom_handler)();
void * result;
//无限循环,直至成功分配内存或用户没有定制内存分配策略
for (;;)
{
my_oom_handler = _malloc_alloc_oom_handler;
if (my_oom_handler == nullptr)//如果用户没有定制内存分配策略
exit(1);
(*my_oom_handler)();//使用用户定制的方法
result = malloc(n);
if (result)
return result;
}
}
template<int inst>
void * _malloc_alloc_template<inst>::oom_realloc(void * p, size_t n)
{
//此函数的设计思路与oom_malloc如出一辙
void(*my_oom_handler)();
void * result;
for (;;)
{
my_oom_handler = _malloc_alloc_oom_handler;
if (my_oom_handler == nullptr)
exit(1);
(*my_oom_handler)();
result = realloc(p,n);
if (result)
return result;
}
}