STL容器可以保存任何C++语言支持的基本类型、用户自定义类型的对象。容器本身只是实现了一种数据结构,对用户数据进行管理。用户数据最终都是保存在内存中的,那么内存的申请、释放工作是由“空间配置器”承担的。标准c++中都提供了std::allocator类。
当容器中保存的是用户自定义类型数据时,有的数据类型结构简单,占用的空间小;而有的数据类型结构复杂,需要的内存空间大。有的应用程序,需要频繁的进行数据元素的插入、删除操作,这就对应这频繁的内存空间的申请、释放工作。大家都知道频繁的内存操作,会产生严重的性能问题。为了解决这些问题,stlport提供了两个空间配置器。一个是“简单空间配置器”,只是对c运行时库中malloc/free函数进行了简单的封装,唯一不同的是,提供类似new异常处理机制。另一个是“基于内存池的空间配置器”,容器每次申请内存的时候,空间配置器会基于一定的策略,向os申请交大的内存,避免每次内存申请都向os申请。
下面用图片说明STL具有次级空间配置能力。

根据上图说明,我们可以写出很简陋地STL空间配置器代码:
/*
__malloc_alloc_template.h文件
** 用来模拟STL中的第一级空间配置器,充分体现了是将申请内存与构造函数分离开来处理,该allocator只负责申请和销毁空间,
采用原始maolloc和free函数管理内存,自己动手写异常处理函数
*/
#ifndef __MALLOC_ALLOC_TEMPLATE_H_INCLUDED
#define __MALLOC_ALLOC_TEMPLATE_H_INCLUDED
#include<cstdlib>
#include<cstddef>
namespace juine
{
class __malloc_alloc_template
{
//typedef void (*set_alloc_handle)();
private:
typedef void (*handle)();
static handle set_alloc_handle; //异常处理函数指针,由用户制定设定如何解决异常
static void* oom_malloc(size_t n);// 当分配内存失败的时候,调用该函数重新分配内存
public:
static void* alloc(size_t n)
{
void *temp=malloc(n);
temp=0; //故意的,目的是为了模拟出out of memory时该做些什么
if(!temp)
temp=oom_malloc(n);
return temp;
}
static void dealloc(void* buff,size_t /*n*/)
{
free(buff);
}
static void set_function_handle( void (*temp)())
{
set_alloc_handle=temp;
}
static handle& get_function_handle()
{
return set_alloc_handle;
}
};
void (*__malloc_alloc_template::set_alloc_handle)()=0;// 这个必须写 不然下面函数中将显示set_alloc_handle没有定义
void* __malloc_alloc_template::oom_malloc(size_t n)
{
void *temp=0;
while(!temp)
{
if(set_alloc_handle==0)
{
std::cout<<"you must find a methods to solve the memory problem "<<std::endl;
//抛出异常元素
exit(1);
}
set_alloc_handle();
temp=malloc(n);
}
return temp;
}
}
#endif // __MALLOC_ALLOC_TEMPLATE_H_INCLUDED
接下来是次级空间配置源码:
/*
**
__default_alloc_template.h文件
次级空间配置器
*/
#ifndef __DEFAULT_ALLOC_TEMPLATE_H_INCLUDED
#define __DEFAULT_ALLOC_TEMPLATE_H_INCLUDED
#include<cstddef> // for size_t
#include<cstdlib>
using std::cout;
using std::endl;
namespace juine
{
enum {__ALIGN=8};
enum {__MAX_BYTES=128};
enum {MAXSIZE=__MAX_BYTES/__ALIGN};
union obj
{
obj *next;
char client_data[1];
};
class __default_alloc_template
{
public:
static char *start_pool; //尽管为静态变量,初始化也应该在内的外面
static char *end_pool; //为char指针是为了刚好取得1byte内存
static size_t poolSize;
static obj *free_list[MAXSIZE];
//将n转化为8的倍数
static size_t ROUND_UP(size_t n)
{
return (((n)+__ALIGN-1)&~(__ALIGN-1));
}
//获得所在数组的下标
static size_t POSITION(size_t n)
{
return (((n)+__ALIGN-1)/__ALIGN-1);
}
static size_t poolLeftSize()
{
return end_pool-start_pool;
}
//当free_lisk指针为0时,应该从内存池取出内存挂在在指针下面
//,返回给用户的所需的内存地址(刷新指针数组所指的内存分布)
static void* refill(size_t n) //此处n已是8的倍数
{
size_t objs=20;
char *chunk=chunk_alloc(n,objs);
if(objs==1)
return chunk;
obj *result,*current_obj,*next_obj;
result=(obj*)chunk; //这一块是用来返回给客户端的
//接下来的是将获得的内存节点将其串起来,让以后分配更加方便
free_list[POSITION(n)]=next_obj=(obj*)(chunk+n);
size_t i;
for(i=1;;i++)
{
current_obj=next_obj;
next_obj=(obj*)((char*)current_obj+n);
if(objs-1==i)
{
current_obj->next=NULL;
break;
}
current_obj->next=next_obj;
}
std::cout<<"分配客户端后,"<<n<<"bytes大小的块还剩下:"<<i<<std::endl;
return result;
}
//分配内存池空间,然后返回指向数组指针的首地址
static char* chunk_alloc(size_t n,size_t &objs)
{
char *result=NULL;
size_t totalSize=n*objs;
if(poolLeftSize()>totalSize) //当内存池够大时一般是申请20块
{
result=start_pool;
start_pool+=totalSize;
cout<<"内存空间够大:得到"<<n<<"bytes块20个"<<endl;
return result; //只是返回首地址而已
}
else if(poolLeftSize()>=n) //当内存池大于申请内存大小时
{
objs=poolLeftSize()/n;
result=start_pool;
start_pool+=objs*n;
cout<<"内存空间比较大:得到"<<n<<"bytes块"<<objs<<"个"<<endl;
return result;
}
else
{
//需要申请的内存
size_t size_bytes_to_get=objs*n*2+ROUND_UP(poolSize>>4);
std::cout<<"需要申请的内存大小为:"<<size_bytes_to_get<<std::endl;
if(poolLeftSize()>0) //当内存池中还有零头时,将其分配出去
{
obj *temp;
temp=free_list[POSITION(poolLeftSize())]; //所剩内存一定是一个块!
free_list[POSITION(poolLeftSize())]=(obj*)start_pool;
free_list[POSITION(poolLeftSize())]->next=temp;
start_pool=end_pool=NULL;
}
start_pool=(char *)malloc(size_bytes_to_get);
end_pool=start_pool+size_bytes_to_get;
result=start_pool;
start_pool+=n*objs;
poolSize+=size_bytes_to_get;
return result;
}
}
public:
static void *alloc(size_t n) //n为申请的内存大小
{
obj *result=NULL;
//大于128时调用第一级配置器
if(n>(size_t) __MAX_BYTES)
{
return (malloc_alloc::alloc(n));
}
//得到在free_list所在的位子
result=free_list[POSITION(n)];
if(result==NULL)
{
//调用refill,从内存池中来获得内存,将其挂到链表之下
void *r=refill(ROUND_UP(n));
return r;
}
cout<<"指针数组还有内存,已得到指针大小"<<endl;
free_list[POSITION(n)]=result->next;
return result;
}
//释放内存
static void dealloc(void *buff,size_t n)
{
if(n>(size_t) __MAX_BYTES)
{
malloc_alloc::dealloc(buff,n);
}
int pos=POSITION(n);
((obj*)buff)->next=free_list[pos];
free_list[pos]=(obj*)buff;
}
//无意义,纯粹为了标准接口
typedef void (*handle)();
static handle& get_function_handle()
{
handle p;
return p;
}
};
//初始化数据
char* __default_alloc_template::start_pool=NULL;
char* __default_alloc_template::end_pool=NULL;
size_t __default_alloc_template::poolSize=0;
obj* __default_alloc_template::free_list[MAXSIZE]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
}
#endif // __DEFAULT_ALLOC_TEMPLATE_H_INCLUDED
// test_alloc.cpp
#include <iostream>
#include<cstring>
#include<cstdlib>
#define _USE_ALLOC //用过是否定义这个变量,确定使用的是哪个配置器
#include"simple_alloc.h"
using namespace std;
using namespace juine;
struct student
{
int id;
string name;
student(int _id,string _name):id(_id),name(_name){}
};
ostream& operator<<(ostream &os,const student& ss)
{
os<<"id:"<<ss.id<<" "<<"name:"<<ss.name;
return os;
}
int main(void)
{
simple_alloc<student> ss;
student *p=ss.alloc(3*sizeof(student));
student *q=ss.alloc(3*sizeof(student));
student *t=ss.alloc(3*sizeof(student));
//for(;i<3;i++) //次级配置中的一个缺陷,其实p指针应该只是指向5个int的,不知到怎么判断越界了没
construct(p,student(1,"小zhang")); //用来构造对象
construct(p+1,student(2,"小lan"));
construct(p+2,student(3,"小s"));
int i;
for(i=0;i<3;i++)
cout<<p[i]<<endl;
destroy(p);
cout<<p[0]<<endl;
}
其测试结果代码如下:
当时用第一及空间配置器时结果如下:
使用次级配置空间时:
最后结果基本达到预期的想法,实现了空间配置器。
下次,接下来是迭代器的实现!
原文:http://blog.csdn.net/bb2b2bbb/article/details/22917745