作为STL的六大组件之一——空间配置器,它的作用就是分配和管理空间,为容器包括算法提供生存空间。然而在我们使用STL中的利器在程序的舞台上大放光彩时,往往看不到它的存在,它总是站在舞 台的背后,为我们精彩的演出默默的付出。今天我们就来看下空间配置器。
1. 为什么需要空间配置器呢
(1)通常申请的小块内存,容易造成内存碎片,最终造成空间的浪费。
(2)当我们用malloc申请空间时,底层会给我们一个struct结构体来管理这块空间,管理是需要成本的。
(3)每次都向系统要小块的内存,效率太低。
(4)如果使用不当,容易忘记释放空间,造成内存泄漏。
(5)提高代码复用率,比如vector和List都有malloc申请空间的重复代码。
2. STL中空间配置器实现的要求
(1)具有空间不足时的应对措施。
(2)隐藏实际中对存储空间的分配和释放的细节,确保所有被分配的存储空间最终都可以被释放。
(3)向system heap中要空间。
(4)考虑多线程状态(muilty_thread)。
(5)考虑过多“小型区块”造成的内存碎片问题。
3. 实现的细节
(1)当用户申请的空间大于等于128个字节时,使用一级空间配置器。当用户申请的空间小于128个字节时,使用二级空间配置器。
(2)一级空间配置器的实现主要由malloc 为我们申请空间,并提供了空间不足(out_of_memory)时的应对措施——释放不用的空间。
(3)二级空间配置器分为两个部分,一个是内存池(直接向系统堆中申请内存),一个是维护16个自由链表,负责16种小型区块的配置能力。
(4)在二级空间配置器中,不管用户申请的空间是多少个字节,根据一个索引函数,将用户申请的字节数上调至8的整数倍,没每次都分配给用户8的整数倍个字节。
(5)二级空间配置器的主要实现过程
4. 代码的具体实现
(1)一级空间配置器
typedef void(*pmalloc_oom_handle)(); //定义函数指针类型
template <int inst>
class malloc_alloc_template{
private:
static pmalloc_oom_handle _mallocHandle; //静态成员变量
public:
static void* allocate(size_t size)
{
__TRACE_DEBUG("一级空间配置器:%d \n", size);
void* result = malloc(size); //直接malloc 开辟空间
if (result == 0)
result = oom_malloc(size); //oom_malloc空间不足的应对措施
return result;
}
static void deallocate(void* ptr, size_t size)
{
__TRACE_DEBUG("一级空间配置器释放空间:%d\n ", size);
free(ptr); //直接调用free释放空间
}
static void* Reallocate(void* ptr, size_t newsize,size_t oldsize)
{
void* result = realloc(newsize);
if (result == 0)
result = oom_realloc(ptr,newsize);
return result;
}
static void dereallocate(void*ptr, size_t size)
{
free(ptr);
}
private:
static void* oom_malloc(size_t size)
{
__TRACE_DEBUG("一级空间配置器,内存不足oom:%d \n", size);
pmalloc_oom_handle my_malloc_handle;
void* result;
for (;;)
{
my_malloc_handle = _mallocHandle;
if (my_malloc_handle == NULL) //如果在空间不足时没有定义应对措施处理函数,就抛异常。
{
throw std::bad_alloc();
}
//尝试去释放已经获取但是不用的空间
my_malloc_handle();
result = malloc(size);
if (result)
return result;
}
}
pmalloc_oom_handle set_malloc_oom_handle(pmalloc_oom_handle mallocHandle)
{
pmalloc_oom_handle old = _mallocHandle;
_mallocHandle = mallocHandle;
return (old);
}
};
//类外初始化静态成员变量
pmalloc_oom_handle malloc_alloc_template<0>::_mallocHandle = NULL;
(2)二级空间配置器
template<int inst>
class DefaultAllocateTemplate
{
public:
static void* Allocate(size_t size)
{
if (size > 128)
return malloc_alloc_template<0>::allocate(size); //大于128调用一级空间配置器
__TRACE_DEBUG("二级空间配置器:%d\n ", size);
size_t index = freeListindex(size);
if (_freeList[index] == NULL) //链表中没有空间,调用refill
return Refill(ROUND_UP(size));
void* result = _freeList[index]; //链表空间充足,返回给用户
_freeList[index] = _freeList[index]->_freeListLink;
return result;
}
static void Deallocate(void* ptr,size_t size)
{
if (size > 128)
return malloc_alloc_template<0>::deallocate(ptr,size);
__TRACE_DEBUG("二级空间配置器释放:%d \n", size);
size_t index = freeListindex(size); //二级空间配置器并不是真正的释放空间,而是将内存块归还至链表
OBJ* cur = (OBJ*)ptr;
cur->_freeListLink = _freeList[index];
_freeList[index] = (OBJ*)ptr;
}
private:
static void* Refill(size_t size)
{
__TRACE_DEBUG("二级空间配置器refill:%d\n ", size);
int objs = 20;
char* chunk = (char*)chunk_alloc(size, objs); //调用chunk_alloc函数向内存池中找
if (objs == 1)
return chunk;
size_t index = freeListindex(size);
OBJ* cur = (OBJ*)(chunk + size);
while (--objs)
{
cur->_freeListLink = _freeList[index]; //头插法将剩余空间连接在index位置
_freeList[index] = cur;
cur = (OBJ*)((char*)cur + size);
}
return chunk;
}
static void* chunk_alloc(size_t size, int& objs)
{
size_t NeedSize = size*objs; //需要的objs个字节数
size_t LeaveSize = _startFree - _endFree; //剩余字节数
char* result; //返回的字节数
if (LeaveSize >= NeedSize)
{
__TRACE_DEBUG("二级空间配置器,内存池空间足够,返回%d个%d字节的内存块\n ", objs, size);
result = _startFree + NeedSize;
return result;
}
else if (LeaveSize >= size)
{
size_t n = LeaveSize / size;
__TRACE_DEBUG("二级空间配置器,内存池不太够,返回%d个%d字节的内存块\n ", n, size);
objs = n;
result = _startFree + n*size;
return result;
}
else
{
size_t index = freeListindex(LeaveSize);
if (LeaveSize > 0)
{
OBJ* cur = (OBJ*)_startFree;
cur->_freeListLink = _freeList[index];
_freeList[index] = cur;
}
size_t Getbytes = 2 * NeedSize + ROUND_UP(_heapsize >> 4);
__TRACE_DEBUG("二级空间配置器,内存池不够了,%d字节的内存块 \n", Getbytes);
_startFree = (char*)malloc(Getbytes); //向堆中申请
if (_startFree == 0)
{
//如果在堆中申请失败,先在链表中找
__TRACE_DEBUG("二级空间配置器,内存池不够了,在堆中找失败,\
在链表中找, %d字节的内存块\n ", (index + 1)*_ALIGN);
int index = freeListindex(size);
for (int i = index; i < _NFREELISTS; i++)
{
OBJ* cur = _freeList[index];
if (cur != NULL)
{
_startFree = (char*)cur;
_freeList[index] = cur->_freeListLink;
_endFree = _startFree + (index + 1)*_ALIGN;
return chunk_alloc(size,objs);
}
}
//如果链表中没有,则在一级空间配置器中找
__TRACE_DEBUG("二级空间配置器,内存池不够了,在堆中找失败,\
在链表中找, %d字节的内存块\n ", Getbytes);
_endFree = 0;
_startFree = (char*)malloc_alloc_template<0>::allocate(Getbytes);
}
__TRACE_DEBUG("二级空间配置器,内存池不够了,在堆中找成功,\
%d字节的内存块\n ", Getbytes);
_heapsize += Getbytes;
_endFree = _startFree + Getbytes;
return (chunk_alloc(size, objs));
}
}
private:
static size_t ROUND_UP(size_t bytes) //将申请的空间上调至8的整数倍
{
return (((bytes)+_ALIGN - 1)&~(_ALIGN - 1));
}
static size_t freeListindex(size_t bytes) //计算索引函数
{
return (((bytes)+_ALIGN - 1) / _ALIGN - 1);
}
private:
enum {_ALIGN=8}; //每次调整的字节数
enum{_MAX_BYTES=128}; //
enum { _NFREELISTS = _MAX_BYTES / _ALIGN }; //数组的大小
union OBJ
{
OBJ* _freeListLink;
char clientData[1];
};
private:
static char* _startFree; //标记空间的起始地址
static char* _endFree; //标记空间的结束地址
static size_t _heapsize; //向堆中申请的字节数
static OBJ* _freeList[_NFREELISTS]; //存放OBJ的指针
};
//静态成员变量类外初始化
template <int inst>
char* DefaultAllocateTemplate<inst>::_startFree = NULL;
template <int inst>
char* DefaultAllocateTemplate<inst>::_endFree = NULL;
template <int inst>
size_t DefaultAllocateTemplate<inst>::_heapsize = NULL;
template <int inst>
typename DefaultAllocateTemplate<inst>::OBJ* DefaultAllocateTemplate<inst>::\
_freeList[DefaultAllocateTemplate<inst>::_NFREELISTS] = { 0 };
(3)为了让用户使用方便,我们将一级空间配置器和二级空间配置器进行封装
#ifdef USE_MALLOC
typedef malloc_alloc_template<0> _Alloc;
#else
typedef DefaultAllocateTemplate<0> _Alloc;
#endif
template <class T,class Alloc>
class Simpleallocate
{
public:
static T* allocate(size_t n) //申请n个类型为T的字节大小
{
return 0 == n ? 0 : (T*)(_Alloc::Allocate(n*sizeof(T)));
}
static T* allocate(void) //申请T字节大小
{
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));
}
};
(4)我们调用一个特殊的函数对我们写的空间配置器进行调试
#include <stdarg.h>
#define _DEBUG_
static string GetFileName(const string& path)
{
char ch = '/' ;
#ifdef _WIN32
ch = '\\';
#endif
size_t pos = path.rfind(ch);
if (pos == string::npos)
return path;
else
return path.substr(pos + 1);
}
inline static void _trace_debug(const char * funcName, const char * fileName, int line, char* format, ...)
{
#ifdef _DEBUG_
fprintf(stdout, "[%s:%d]%s", GetFileName(fileName).c_str(), line, funcName);
// 输出用户信息
va_list args;
va_start(args, format);
vfprintf(stdout, format, args);
va_end(args);
#endif
}
#define __TRACE_DEBUG(...) _trace_debug(__FUNCDNAME__, __FILE__, __LINE__, __VA_ARGS__);
二级空间配置器的实现还是有点复杂的,需要理清思路,明白原理。