1.空间配置器概述:
空间配置器就是为了给要执行的作业提供内存资源或者硬盘或者其他辅助资源。
c++的内存配置的基本操作是::operator new(),内存释放的基本操作是::operator
delete()。这两个函数就是调用的是c
语言里的malloc()和 free()函数。所以SGI就是以
malloc()和 free()来完成内存的配置与释放。
在前边的简易内存池的实现的文章中,我们就是为了减少因为太多的小块内存的申请而造成空间的浪费,所以才引入了内存池,在stl的空间配置器里,也是考虑了这样的问题,所以,
SGI设计出了两级配置器,一级配置器主要用来分配大块内存(大于128字节),二级空间配置器是分配小块内存(小于128字节)。一级空间配置器主要就是调用c语言中的malloc来分配空间,free函数来释放空间,而二级空间配置器就是通过类似于简易内存池的实现办法,下边我将STL中使用的空间配置器图示说明:
2.空间配置器的重要函数的具体实现过程:
1)refill函数:是在对应大小的自由链表的下边没有可以使用的空闲缓冲块的时候,需要调用此函数来填充自由链表。
2)chunk_alloc函数:就是去内存池中申请空间去使用,如果没有可以使用 的,则去使用malloc去开辟。本文的第一张图片展示了该函数的流程图。
下边来具体剖析该函数代码:
3)几个设计比较巧妙的函数:
3.关于空间配置器
1) 模拟实现了一遍空间配置器,发现,空间配置器并没有实现对内存的释放,这样会不会造成内存泄漏?答案是不会的,因为,我们一般不会分配很多的内存给内存池,所以,程序结束后,操作系统就会收回那些空间。然而,对于那些大程序,使用完之后,不释放,就会内存泄漏。
这就让我想到了曾经的一个想法:
很久一段时间之前,我就是这么想:之前写代码的时候,写了malloc经常没有free,难道就是一直内存泄漏吗??难道我的程序还是一直占着自己申请的那块内存吗?
很有意思。以前太幼稚。
2)程序中的成员变量和成员函数都是静态的,这样就是保证,这些成员不是属于对象而是属于类,是全局的。还有静态成员函数在类内声明的时候加上static,在类外进行定义的时候就不要加上 static。
4.自己实现的空间配置器的测试办法:
自己写出一个trace跟踪,并且将所有的输出信息写进一个日志文件。
#define __DEBUG__
FILE* fOut = fopen("trace.log", "w");
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);
}
// 用于调试追溯的trace log
inline static void __trace_debug (const char* function,
const char * filename, int line , char* format , ...)
{
// 读取配置文件
#ifdef __DEBUG__
// 输出调用函数的信息
fprintf(stdout , "【%s:%d】%s" , GetFileName (filename). c_str(), line , function);
fprintf(fOut, "【%s:%d】%s" , GetFileName (filename). c_str(), line , function);
// 输出用户打的trace信息
va_list args ;
va_start (args , format);
//fprintf(stdout,"%c",'\n');
vfprintf (stdout , format, args);
vfprintf (fOut , format, args);
fprintf(fOut,"%c",'\n');
fprintf(stdout,"%c",'\n');
va_end (args );
#endif
}
#define __TRACE_DEBUG(...) \
__trace_debug(__FUNCTION__ , __FILE__, __LINE__, __VA_ARGS__);
5.代码实现:
// Trace 跟踪
#define __DEBUG__
FILE* fOut = fopen("trace.log", "w");
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);
}
// 用于调试追溯的trace log
inline static void __trace_debug (const char* function,
const char * filename, int line , char* format , ...)
{
// 读取配置文件
#ifdef __DEBUG__
// 输出调用函数的信息
fprintf(stdout , "【 %s:%d】%s" , GetFileName (filename). c_str(), line , function);
fprintf(fOut, "【 %s:%d】%s" , GetFileName (filename). c_str(), line , function);
// 输出用户打的trace信息
va_list args ;
va_start (args , format);
//fprintf(stdout,"%c",'\n');
vfprintf (stdout , format, args);
vfprintf (fOut , format, args);
fprintf(fOut,"%c",'\n');
fprintf(stdout,"%c",'\n');
va_end (args );
#endif
}
#define __TRACE_DEBUG(...) \
__trace_debug(__FUNCTION__ , __FILE__, __LINE__, __VA_ARGS__);
typedef void(*pFun)();
template<int inst>
class __MallocAllocTemplate
{
private:
static void* OomMalloc(size_t);//malloc申请空间失败,会调用该函数企图系统释放空间
//static void(* __MallocAllocOomHandler)();
static pFun __MallocAllocOomHandler;//函数指针句柄
public:
static void* Allocate(size_t n)
{
__TRACE_DEBUG("向一级空间配置器申请了%u个字节",n);
void* result = malloc(n);
if(result == 0)
result = OomMalloc(n);
return result;
}
static void Deallocate(void* ptr,size_t n)
{
__TRACE_DEBUG("一级空间配置器释放了0x%p:%u个字节",ptr,n);
free(ptr);
ptr = NULL;
}
static pFun SetMallocHandler(pFun f)
{
__TRACE_DEBUG("一级空间配置器企图系统释放空间");
pFun old = __MallocAllocOomHandler;
__MallocAllocOomHandler = f;
return old;
}
};
template<int inst>
pFun __MallocAllocTemplate<inst>::__MallocAllocOomHandler = 0;
template<int inst>
void* __MallocAllocTemplate<inst>::OomMalloc(size_t n)
{
__TRACE_DEBUG("一级空间配置器申请%u个字节失败,调用OomMalloc",n);
pFun myMallocHandler;//定义一个函数指针
void* result;
while(1)
{
myMallocHandler = __MallocAllocOomHandler;
if(myMallocHandler == 0)
throw std::bad_alloc();
myMallocHandler();//调用系统关于内存不足的处理例程,企图释放空间
result = malloc(n);
if(result)
return result;
}
}
//二级空间配置器
enum{__ALIGN = 8};//上调边界
enum{__MAX_BYTES = 128};//自由链表的区块的上限
enum{__N_FreeListS = __MAX_BYTES/__ALIGN};//自由链表的长度
template<bool threads,int inst>
class __DefaultAllocTemplate
{
public:
static void* Allocate(size_t n)
{
Obj* volatile * my_FreeList;
Obj* result;
if(n > 128)
{
return (__MallocAllocTemplate<inst>::Allocate(n));
}
__TRACE_DEBUG("向二级空间配置器申请了%u个字节",n);
my_FreeList = __FreeList + FreeListIndex(n);
result = *my_FreeList;
if(result != NULL)//自由链表中还有空的缓冲块供程序使用
{
__TRACE_DEBUG("自由链表的%u位置还有空的缓冲块可供使用",FreeListIndex(n));
*my_FreeList = result->__FreeListLink;
return result;
}
//自由链表中没有空的缓冲块
void* r = Refill(RoundUp(n));
return r;
}
static void Deallocate(void* p,size_t n)
{
if(n > 128)
{
__MallocAllocTemplate<0>::Deallocate(p,n);
return;
}
//将其插入到自由链表的对应的位置
size_t index = FreeListIndex(n);
__TRACE_DEBUG("自由链表的%u位置插入空的缓冲块",index);
//头插
Obj* volatile * my_FreeList = __FreeList + index;
((Obj*)p)->__FreeListLink = *my_FreeList;
*my_FreeList = (Obj*)p;
}
private:
union Obj
{
union Obj* __FreeListLink;
char _data;
};
static size_t RoundUp(size_t bytes)//将给定的字节数上调至8的倍数
{
return ((bytes + __ALIGN -1) & ~(__ALIGN - 1));
}
static size_t FreeListIndex(size_t bytes)//找给定的字节数在自由链表中对应的下标
{
return ((bytes + __ALIGN - 1) / __ALIGN - 1);
}
static void* Refill(size_t n);
static char* ChunkAlloc(size_t size,int& nObjs);
private:
static Obj* volatile __FreeList[__N_FreeListS];
static char* _startFree;
static char* _endFree;
static size_t _heapSize;
};
//静态成员变量的初始化
template<bool threads,int inst>
char* __DefaultAllocTemplate<threads,inst>::_startFree = 0;
template<bool threads,int inst>
char* __DefaultAllocTemplate<threads,inst>::_endFree = 0;
template<bool threads,int inst>
size_t __DefaultAllocTemplate<threads,inst>::_heapSize = 0;//从堆中申请的内存之和
template<bool threads,int inst>
typename __DefaultAllocTemplate<threads,inst>::Obj * volatile
__DefaultAllocTemplate<threads,inst>::__FreeList[__N_FreeListS] =
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
template<bool threads,int inst>
void* __DefaultAllocTemplate<threads,inst>::Refill(size_t n)
{
int nObjs = 20;
__TRACE_DEBUG("自由链表的%u位置没有空的缓冲块可供使用,填充%u个",FreeListIndex(n),nObjs);
char* chunk = ChunkAlloc(n,nObjs);
Obj* volatile * myFreeList;
Obj* result = NULL;
Obj* cur;
Obj* next;
//如果仅仅申请到一个对象,直接返回
if(nObjs == 1)
return (Obj*)chunk;
//申请多个对象
myFreeList = (__FreeList + FreeListIndex(n));
result = (Obj*)chunk;
//将剩余的块挂在自由链表的下边
*myFreeList = cur = (Obj*)(chunk + n);
for(int i = 0; i < nObjs - 2; ++i)
{
next = (Obj*)((char*)cur + n);
cur->__FreeListLink = next;
cur = next;
}
cur->__FreeListLink = NULL;
return result;
}
template<bool threads,int inst>
char* __DefaultAllocTemplate<threads,inst>::ChunkAlloc(size_t size,int& nObjs)
{
size_t totalBytes = size* nObjs;
size_t bytesLeft = _endFree - _startFree;
char* result;
size_t index;
//内存足够
if(bytesLeft >= totalBytes)
{
__TRACE_DEBUG("内存池中有%u个字节,足以满足需要的%u个字节,可以分配%u个对象",bytesLeft,totalBytes,nObjs);
result = _startFree;
_startFree += totalBytes;
return result;
}
//内存不足,但是足以至少一个对象的大小
else if(bytesLeft >= size)
{
nObjs = bytesLeft / size;
__TRACE_DEBUG("内存池中有%u个字节,不能满足需要的%u个字节,只能分配%u个对象",bytesLeft,totalBytes,nObjs);
totalBytes = nObjs * size;
result = _startFree;
_startFree += totalBytes;
return result;
}
//内存池不足以一个对象的大小
else
{
//去堆上申请一块空间
size_t bytesToGet = 2 * totalBytes + RoundUp(_heapSize>>4);
//内存池中还有内存
if(bytesLeft > 0)
{
index = FreeListIndex(bytesLeft);
Obj* volatile* my_FreeList = __FreeList + index;
//将其挂入自由链表
//头插
__TRACE_DEBUG("内存池中还有内存,它将被挂在自由链表的%u位置下",index);
((Obj*)_startFree)->__FreeListLink = *my_FreeList;
*my_FreeList = ((Obj*)_startFree);
}
//到堆里去申请内存
__TRACE_DEBUG("内存池中没有内存,malloc申请%u个字节",bytesToGet);
_startFree = (char*)malloc(bytesToGet);
if(_startFree == 0)//申请失败
{
__TRACE_DEBUG("malloc申请%u个字节失败",bytesToGet);
//去自由链表的后边的链下找
Obj* volatile * my_FreeList;
Obj* p;
for(int i = size; i <= __MAX_BYTES; i += __ALIGN)
{
index = FreeListIndex(i);
my_FreeList = __FreeList + index;
p = *my_FreeList;
if(p != 0)//找到的自由链下还有空闲的块
{
__TRACE_DEBUG("在自由链表的%u位置找到空闲的缓冲块",index);
//取出一块
//头删
*my_FreeList = p->__FreeListLink;
_startFree = (char*)p;
_endFree = _startFree + i;
return ChunkAlloc(size,nObjs);
}
}
//下边的申请抛出异常,得保证_startFree和_endFree相等。
_endFree = 0;
//找一级空间配置器
__TRACE_DEBUG("malloc没有申请成功,在自由链表中也没有找到空的缓冲块,调用一级空间配置器分配");
_startFree = (char*)__MallocAllocTemplate<0>::Allocate(bytesToGet);
}
__TRACE_DEBUG("空间申请成功");
_heapSize += bytesToGet;
_endFree = _startFree + bytesToGet;
return ChunkAlloc(size,nObjs);
}
}
//测试一般场景
void TestDefaultAllocTemplate1()
{
__DefaultAllocTemplate<false,0> d;
char* p1 = (char*)d.Allocate(8);
char* p2 = (char*)d.Allocate(130);
}
//测试分配释放的场景
void TestDefaultAllocTemplate2()
{
int begin = GetTickCount();
__DefaultAllocTemplate<false,0> d;
vector<pair<void*,int> >v;
v.push_back(make_pair(d.Allocate(130),130));
for(int i = 0; i < 100;++i)
{
v.push_back(make_pair(d.Allocate(28),28));
}
while(!v.empty())
{
d.Deallocate(v.back().first,v.back().second);
v.pop_back();
}
for(int i = 0; i < 100;++i)
{
v.push_back(make_pair(d.Allocate(28),28));
}
while(!v.empty())
{
d.Deallocate(v.back().first,v.back().second);
v.pop_back();
}
int end = GetTickCount();
cout<<end - begin<<endl;
}
6.输出结果展示:
控制台输出:
日志文件输出: