空间配置器
1.STL的空间配置器干什么用,解决了什么样的问题
用来解决(回忆一下os的页内碎片)和外碎片的问题。(防止小型区块所可能造成的内存破碎问题)
a. 内碎片:申请了4B/8B内存,但只用了3B(内存字节对齐【访问效率考虑】)。即申请的没用完
b. 外碎片:申请的时候无规律,大内存与大内存之前出现很多小的不连续的内存块,因无法利用而浪费掉
2.空间配置器的实现策略
空间配置器将new(delete)操作分了开来,operator new分配内存空间+在已经得到的内存上构建对象。这里主要讲述空间配置器的内存管理
a. 用户申请空间大于128B?如果是调用一级空间配置器,如果不是调用二级空间配置器。
a) 一级空间配置器:用于分配大块内存,直接调用包装了一下malloc和free。这样的话大块内存用完可以立马释放掉。
b) 二级空间配置器:用于分配小块内存,防止小块内存的申请造成内存破碎的问题。内存池(和线程池啊,对象池啊一个作用。每次申请一大块内存,作为内存池,在这个内存池上建16个自由链表。用户申请内存管自由链表要,自由链表没内存了管内存池要,内存池没内存了管malloc要)+16个自由链表(就是8B,16B···128B大小的16个链表,链表下挂着n多个xB大小的内存块们,用的时候,直接摘下来用,用完之后归还)。
i. 【流程】小于等于128B进二级空间配置器
1. 链表中有空闲节点:直接取走返回
2. 链表中无空闲节点:向内存池申请节点
a) 内存池中有空间:取内存到链表,返回给用户
b) 内存池中无空间:malloc,补充内存池空间,逐级调用取内存
ii. 【二级空间配置器中的内存释放问题猜测】
1. 是不是要等stl这个配置类对象销毁的时候才会释放掉二级空间配置器所申请的内存?
b. 这里没有总结用类型萃取获取对象类型,然后在分配得到的内存上构造对象的内容
-----------------------------------------------------------以下是以前的记录------------------------------------------------
1.空间配置器的标准接口
2.具有次配置力的SGI空间配置器(SGI特殊的空间配置器std::alloc)simple_jjallocator.h
test.cpp#ifndef SIMPLE_JJALLOC_H #define SIMPLE_JJALLOC_H #include<new> //for placement new #include<cstddef> //for ptrdiff_t,size_t #include<cstdlib> //for exit() #include<climits> //for UINT_MAX十进制的最大值 #include<iostream> //for cerr namespace JJ { /*****************ptrdiff_t与size_t类型*****size_type与difference_type类型******************** ****ptrdiff_t:signed类型,通常用于两指针减法操作的结果,它可以是负数。(因为指针相减有正有负) ****size_t:unsigned类型,用于指明数组长度,它是非负整数。 ****size_type:unsigned类型,容器中元素长度或下表,vector<int>::size_type i=0; ****difference_type:signed类型,表示迭代器差距,vector<int>::difference_type=iter1-iter2 ****前两者位于标准类库std内,后两者为stl对象所有 *********************************************************************************************/ template<class T> inline T* _allocate(ptrdiff_t size, T*) { std::cout<<"I'm _allocate in simple_jjalloc!"<<std::endl; /**************************new_handler与set_new_handler*********************************** ****new_handler:内存分配失败后,调用的处理函数。 ****set_new_handler:参数是被指定的new_handler函数指针,返回参数也是new_handler是被替换掉的new_handler *****************************************************************************************/ std::set_new_handler(0); /****************::*********************************************************************** ****"::":全局作用。比如::luo这就是个全局变量,而luo这是个局部变量 ****"::":类作用。比如Node::function() ****"::":名字空间。比如std::size_t *****************************************************************************************/ T *tmp=(T*)(::operator new((size_t)(size*sizeof(T)))); if(tmp==0)//没有前面的std::set_new_handler(0);把内存分配失败后的异常调用函数给替换掉,就执行不到这儿 { std::cout<<"failed!"<<std::endl; std::cerr<<"out of memory"<<std::endl; exit(1); } return tmp; } template<class T> inline void _deallocate(T* buffer) { ::operator delete(buffer); } /************************************new的三种形态******************************************* ****new operator:就是平常用的new,通常做三件事,1.用operator new分配内存给对象,2.调用构造函数初始化那块内存,3.将地址转给对象指针 如果仅仅是在堆上建立对象,那么应该使用new operator,它会提供周全的服务 ****operator new:在默认情况下首先会调用分配内存的代码,尝试从堆上得到一段空间,成功就返回,失败就调用new_hander,重复前面过程,直到抛出异常 如果仅仅是分配内存,那么应该调用operator new,但初始化不在它的职责之内。若对默认的内存分配过程不满意,那就重载它 ****placement new:用来实现定位构造,可以通过它来选择合适的构造函数。 如果想在一块已获得的内存里建立一个对象,那就改用placement new ********************************************************************************************/ template<class T1,class T2> inline void _construct(T1* p,const T2& val) { new(p) T1(val);//p为那块内存地址,T1()为指定构造函数;此句为p->T1::T1(val); std::cout<<"I'm _construct!"<<std::endl; } template<class T> inline void _destroy(T* ptr) { std::cout<<"I'm _destroy!"<<std::endl; ptr->~T(); } template<class T> class mallocator { public: typedef T value_type;//为什么要重新定义,原因在章三 typedef T* pointer; typedef const T* const_pointer; typedef T& reference; typedef const T& const_reference; typedef size_t size_type; typedef ptrdiff_t difference_type; template<class U> struct rebind//干吗用?见下 { typedef mallocator<U> mother; }; pointer allocate(size_type n,const void* hint=0) { return _allocate((difference_type)n,(pointer)0); } void deallocate(pointer p,size_type n) { _deallocate(p); } void construct(pointer p,const_reference val) { _construct(p,val); } void destroy(pointer p) { _destroy(p); } pointer address(reference x) { return (pointer)&x; } const pointer const_address(const_reference x) { return (const pointer)&x; } size_type max_size()const { return size_type(UINT_MAX/sizeof(value_type)); } }; } #endif
result#include "simple_jjalloc.h" #include<vector> #include<iostream> using namespace std; int main() { { int ia[5]={0,1,2,3,4}; unsigned int i; vector<int,JJ::mallocator<int> > iv(ia,ia+5);//使用了JJ::mallocator的alloc来分配内存 for(int i=0;i<iv.size();i++) { cout<<iv[i]<<"\t"; } cout<<endl; //为什么没有用JJ::mallocator的destroy来回收内存,而自动用了vector的析构函数来回收 ????明明指定了空间配置器是JJ::mallocator了啊 } return 0; }
allocator::rebind
allocator::rebind 部分转自:http://bbs.csdn.net/topics/200079053/***************************************************************************************************************************** allocator::rebind A structure that enables an allocator for objects of one type to allocate storage for objects of another type 一个典型的template typedef,作用就是: 先看vector的分配器是allocator<T>,你要多少T就给你他就给你多少个T的对象。和new出来的一样。 再看看list的分配器,同样也是allocator<T>,但是了解链表这个数据结构的都知道一个node应该是 template<class T> struct node { T data; node* next; }; 所以allocator<T>就无法分配出一个node对象,而仅仅是T对象,像vector的那样。 尽管list的allocator<T>和vector的allocator<T>是一样,但是list还是有其他的办法,它在内部由allocator<T>来获取适合它自己的分配器。 allocator<T>::rebind<node<T> >::other,这个other就是allocator<node<T> >,list就用这个other来分配内存了。 *****************************************************************************************************************************/
3.内存基本处理工具(stl_uninitalized.h)new分为两步:1、allocate分配一块内存;2、在这块内存上构建一个对象。大致图如下:
a.stl_constructor.h(内存配置完后的对象构造行为和内存释放前的对象析构行为)
#ifndef STL_CONSTRUCT_H #define STL_CONSTRUCT_H #include<new.h>//involve placement new /***********************value_type()和_type_traits<T>***************************** *****value_type:用以判别数值类型 *****_type_traits:用以判别该类型的析构函数是否无关痛痒(trivial还是non-trivial) *****c++本身并不支持判别(指针所指之物的类型)和(该类型的析构函数是否无关痛痒)。所以上述 value_type和_type_traits是需要自己实现的,具体实现在章三 *********************************************************************************/ template<class T1,class T2> inline void construct(T1* p,const T2& val) { new (p) T1(val);//placement new;指定调用T1的构造函数T1(value)来初始化指针p所指向的内存 } //destroy()的第一个版本,接受一个指针 template<class T> inline void destroy(T* p) { p->~T();//调用指针p所指对象的析构函数,用以销毁对象 } //destroy()的第二个版本,接受两个迭代器,此函数设法找出元素的数值类型 //进而利用_type_traits<>求取最适当措施 template<class ForwardIterator> inline void destroy(ForwardIterator first,ForwardIterator last) { _destroy(first,last,value_type(first)); } /********回顾inside the c++ object model中的trivial(non-trivial)****************** *****trivial和non-trivial是相对于下述四种而言的:1、constructor 2、copy constructor 3、assignment 4、deconstructor *****trivial constructor/copy constructor/assignment/deconstructor:采取的是最有效率的方法,以copy constructor为例, trivial copy constructor 是直接按bitywise的方式拷贝,调用的是memcpy(),比较有效率,但带来的后果是,拷贝只是浅拷贝, 当需要深拷贝,例如对象中有指针时,这种方法就达不到效果了,就需要non-trivial copy constructor了 *****就达到non-trivial的条件而言(以copy constructor为例):1、自己定义了copy constructor 2、基类有non-trivial copy constructor 3、包含一个member object,member object有一个non-trivial constructor 4、虚函数、虚基类涉及到vptr的时候 ********************************************************************************************/ //判断元素的数值类型(value_type)有trivial destructor还是有non-trivial destructor template<class ForwardIterator,class T> inline void _destroy(ForwardIterator first,ForwardIterator last,T*) { /******************************typename**************************************** *****1、关键词class的同义词:用于泛型编程 *****2、类型名指示符:如下列,对于_type_traits<T>::has_trivial_destructor而言, 编译器并不知晓,它是一个_type_traits<T>变量还是一个_type_traits<T>中的内建类型 ******************************************************************************/ typedef typename _type_traits<T>::has_trivial_destructor trivial_destructor; _destroy_aux(first,last,trivial_destructor()); } //判断元素的数值类型(value_type)有non-trivial destructor template<class ForwardIterator> inline void _destroy_aux(ForwardIterator first,ForwardIterator last,_false_type) { for(;first<last;++first) { destroy(& *first); } } //判断元素的数值类型(value_type)有trivial destructor template<class ForwardIterator> inline void _destroy_aux(ForwardIterator first,ForwardIterator last,_true_type) { } //以下是destroy()第二版本针对迭代器为char*和wchar*的特化版 inline void destroy(char*,char*) { } inline void destroy(wchar_t* ,wchar_t*) { } #endif
![]()
destroy支持指针析构和析构掉两个迭代器之间的对象,其中第二个版本(支持析构掉两个迭代器之间的版本),需要析构一大堆的对象,如果这些对象的析构函数都是trivial的,那就没有必要一个个析构了(考虑到效率)。所以版本二在析构这一堆对象之前先判断了一下该类型的析构函数是否是non-trivial,若是,那就老老实实一个一个析构,若不是,那就什么也不做直接结束算了。
b.空间的配置与释放
对于对象构造前的内存配置和对象析构后的内存释放主要需要考虑四个问题:
1、向system heap申请内存
2、考虑多线程状态
3、考虑内存不足时的应变措施
4、考虑过多“小型区块”可能造成的内存碎块问题
多线程状态先放一下,对于第四点:“考虑到小型区块所可能造成的内存破碎问题”,sgi设计了双层级配置器。第一级配置器直接使用malloc和free,第二季配置器则视情况采用不同的策略:当配置区块超过128bytes的时候,视之为足够大,调用第一级配置器,小于128bytes时,视之为足够小,为降低额外负担,采用复杂的memory pool整理方式,而不再求助于第一级配置器。
以下是stl_alloc.h
c.第一级配置器_malloc_alloc_template剖析
第一级配置器使用的是malloc、realloc、free等c函数执行实际的内存配置、释放、重配置操作,所以不能使用set_new_handler(这是operator new的东西),而是需要自己仿照set_new_handler来编写设置处理函数。#if 0 # include<new> # define _THROW_BAD_ALLOC throw bad_alloc #elif !defined(_THROW_BAD_ALLOC) # include<iostream> # define _THROW_BAD_ALLOC std::cerr<<"out of memory!"<<std::endl; exit(1) #endif //以上类似于语言中的if 、else if、else。称为条件编译法。 //以下是第一级配置器 //注意,无"template类型参数"。至于"非template类型参数"(内建类型参数)inst,则完全没有派上用场 template<int inst> class _malloc_alloc_template { /*******************************函数指针定义******************************* *****定义:每个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。 可用该指针变量调用函数,如同用指针变量可引用其他类型变量一样。 #include<iostream> using namespace std; int maxValue(int x,int y)//函数定义 { return (x>y?x:y); } int main() { //函数指针格式:(函数返回类型)(*指针变量名)(形参列表) int (*ptr)(int,int); int a=5; int b=4; ptr=maxValue; //函数指针调用:(*函数指针名)(形参列表) cout<<(*ptr)(a,b)<<endl;//5 } **************************************************************************/ /******************************为什么需要函数指针************************** ****1.函数指针主要是能够用一个指针的方式指向一个函数,并且还可以换换指向别的函数 #include<iostream> using namespace std; void Test1(int x) { cout<<"Test1!"<<endl; } void Test2(int x) { cout<<"Test2!"<<endl; } void Test3(int x) { cout<<"Test3!"<<endl; } //定义一个函数指针类型: typedef(函数返回类型)(*指针变量名)(形参列表) typedef void (* FunctionType)(int); void CallTestx(FunctionType ft,int x) { ft(x); } int main() { CallTestx(Test1,1);//Test1! CallTestx(Test2,2);//Test2! CallTestx(Test3,3);//Test3! } *****2.回调机制就是很好的应用函数指针的例子,这时函数指针作为回调函数的一个参数。 比如你要让windows系统知道在某种事件(如:鼠标按下)发生后该如何响应(或根本不响应), 但系统怎么知道你的程序里有这么一个函数是用来响应鼠标按下的呢?所以在这个函数前面 加一个CALLBACK并把此函数地址赋给系统,windows就知道调用哪个函数来响应哪个事件。 具体可以看看关于回调函数的解释。 ***************************************************************************/ private: /*******************************void*指针******************************* *****指针有两个属性,指向对象的地址和长度,但是指针只是存储地址,长度取决 于指针的类型。比如int*指针从指定地址向后寻找4字节作为变量的存储单元 1、void*指针没有指定类型,所以这个类型的指针不能判断指向对象的长度 2、任何指针都可以赋值给void*指针 3、void*指针赋值给其他类型的指针时都要进行转换 4、void*指针不能参与指针运算,除非进行转换 ***********************************************************************/ //以下都是函数,所代表的函数将用来处理内存不足的情况 //oom:out of memory static void* oom_malloc(size_t); static void* oom_realloc(void *,size_t); //以下是函数指针,所代表的函数将用来处理内存不足的情况 static void (*_malloc_alloc_oom_handler)();//函数指针 public: static void* allocate(size_t n)//第一级配置器分配内存 { void *result=malloc(n);//第一级配置器直接使用malloc() if(result==0) { result=oom_malloc(n);//内存不足,调用函数oom_malloc(n)继续分配 } return result; } static void deallocate(void *p,size_t n)//第一级配置器释放内存 { free(p);//第一级配置器直接使用free() } static void* reallocate(void *p,size_t old_sz,size_t new_sz)//第一级配置器改变指针指向内存大小 { void *result=realloc(p,new_sz);//第一级配置器直接使用realloc()来改变内存大小 if(result==0) { result=oom_realloc(p,new_sz);//改变失败,调用oom_realloc继续改变 } return result; } //以下是仿真c++的set_new_handler(),换句话说,你可以通过它,指定自己的out_of_memory_handler /*********************************************** 下面这个真是把人给搞浑了。查看网上资料说是参数是void(*)()函数指针,这个明白;返回类型也是函数指针,靠! 代码可以写成这样: typedef void(*PF)(); static PF set_malloc_handler(PF f) { void (*old)()=_malloc_alloc_oom_handler; _malloc_alloc_oom_handler=f; return (old); } 这才是给人看的嘛!!! ***********************************************/ static void (*set_malloc_handler(void(*f)()))() { void (*old)()=_malloc_alloc_oom_handler; _malloc_alloc_oom_handler=f;//和set_new_handler一样,把参数作为新的处理函数 return (old); //同时返回的是老的设置函数 } }; //malloc_alloc out-of-memory handling //初值为0,客户端需使用static void (*set_malloc_handler(void(*f)()))()设置分配失败后调用的处理函数 template<int inst> void (* _malloc_alloc_template<inst>::_malloc_alloc_oom_handler)()=0; //处理内存不足时的函数指针初值设为0 template<int inst> void* _malloc_alloc_template<inst>::oom_malloc(size_t n)//分配内存失败就到了这儿,用oom_alloc继续分配 { void (* my_malloc_handler)(); void *result; for(;;) { my_malloc_handler=_malloc_alloc_oom_handler;//获取内存分配失败后处理函数的地址 if(my_malloc_handler==0) { _THROW_BAD_ALLOC;//define _THROW_BAD_ALLOC throw bad_alloc } (*my_malloc_handler)();//调用处理例程,企图释放内存 result=malloc(n);//再次尝试配置内存 if(result) { return (result); } } } template<int inst> void* _malloc_alloc_template<inst>::oom_realloc(void *p,size_t n)//改变内存大小失败就到了这儿,用oom_alloc继续分配 { void (* my_malloc_handler)(); void *result; for(;;) { my_malloc_handler=_malloc_alloc_oom_handler;//获取内存分配失败后处理函数的地址 if(my_malloc_handler==0) { _THROW_BAD_ALLOC;//define _THROW_BAD_ALLOC throw bad_alloc } (*my_malloc_handler)();//调用处理例程,企图释放内存 result=realloc(p,n);//再次尝试改变内存大小 if(result) { return (result); } } } //提供访问,以下直接将参数inst指定为0 typedef _malloc_alloc_template<0> malloc_alloc;
d.第二级配置器_default_alloc_template剖析
第二级内存配置器多了一些机制,避免太多小额区块造成内存的碎片。小额区块带来的不仅是内存碎片,配置时的额外负担也是一个大问题。
第二级内存配置器的做法是,如果区块够大,超过128bytes就移交第一级配置器处理。当区块小于128bytes时,则以内存池(memory pool)管理,此法又称为次层配置:每次配置一大块内存,并维护对应的自由链表(free list)。free list负责小额区块的分配与回收。为方便管理同样SGI的第二级配置器内存使用边界对齐(8的倍数);同时维护16个freelist,8,16,24,32,40···128bytes。freelist节点结构如下:
#include<iostream> using namespace std; /*****************************union性质***************************************** *****1、所有成员都从一个地址存起 *****2、大小为最大成员(所占内存最大)的长度(考虑边界对齐) *****3、所存数据是只有最近存的那个成员有效 union Test { int dI; double dL; char dC; }; int main() { cout<<sizeof(Test)<<endl;//8,大小为最大成员变量double dL所占的内存大小 Test t; t.dC='G'; cout<<"*******************"<<endl; cout<<t.dC<<endl;//G,最近存的char有效 cout<<t.dI<<endl;//乱码 cout<<t.dL<<endl;//乱码 t.dI=1; cout<<"*******************"<<endl; cout<<t.dC<<endl;//乱码 cout<<t.dI<<endl;//1,最近存的int有效 cout<<t.dL<<endl;//乱码 t.dL=2; cout<<"*******************"<<endl; cout<<t.dC<<endl;//乱码 cout<<t.dI<<endl;//0 cout<<t.dL<<endl;//2,最近存的double有效 } *******************************************************************************/ //free list用union存储减少了额外的link的存储空间 union obj { union obj* free_list_link; char client_data[1]; };
![]()
第二级配置器做法:在判断需求内存块小于128bytes时,先从对应的freelist中取空闲块,如果对应的freelist中没有空闲块,就用refill从内存池中取内存来填充该freelist;如果内存池中也没有空余内存,就用chunk_alloc从其他freelist中摘下空闲块来填充内存池;如果其他freelist也没有空闲块了,那么就只能调用第一级配置器,看看out of memory能做些什么事了。/********************************enum类型*************************************** #include<iostream> using namespace std; enum Days { Mon=1, Tues=2, Wed,//标示符=整型常数 Thur, Fri=8, Sat,//9,递增1 Sun//10,递增1 }; int main() { cout<<Wed<<endl;//3 cout<<Sat<<endl;//9 } *******************************************************************************/ /****************************************************************************** 第二级配置器:总的来说,判断区块小于128bytes后,先从相应的free list中找,若free list 没有,就用refill从内存池中取,重新填充free list;若内存池中也没有空余的内存块,就 用chunk_alloc从heap(从未用的其他free list区块中摘下来,划分放到内存池中)中拿。 *******************************************************************************/ #include"_malloc_alloc_template.h" enum{_ALIGN=8};//小型区块的上调边界 enum{_MAX_BYTES=128};//小型区块的上限 enum{_NFREELISTS=_MAX_BYTES/_ALIGN};//free lists个数 //以下是第二级配置器 //注意,无“template类型参数”,且第二参数完全没派上用场 //第一参数用于多线程环境下。 /*******************************类声明*****************************************/ template<bool threads,int inst> class _default_alloc_template { /***********功能函数与变量**********/ private: static size_t ROUND_UP(size_t bytes)//将所需内存bytes上升至8的倍数 { //~按位取反,&按位与 return (((bytes)+_ALIGN-1)&~(_ALIGN-1)); } private: union obj//free list节点构造 { union obj* free_list_link; char client_data[1]; } private: //volatile关键词声明的变量,编译器对该变量的代码访问就不再进行优化,从而提供对特殊地址的稳定访问 static obj* volatile free_list[_NFREELISTS];//16个区块 static size_t FREELIST_INDEX(size_t bytes)//根据区块大小选择从哪个free_list摘下内存进行分配 { return (((bytes)+_ALIGN-1)/_ALIGN-1); } static void* refill(size_t n);//返回一个大小为n的对象,并可能加入大小为n的其他区块到free_list中 static char* chunk_alloc(size_t size,int &nobjs);//配置一大块空间,可容纳nobjs个大小为size的区块 static char *start_free;//内存池的起始位置,只在chunk_alloc()中变化 static char *end_free;//内存池的结束位置,只在chunk_alloc()中变化 static size_t heap_size; /*******暴露在外,实际使用的函数*******/ public: static void* allocate(size_t n);//分配内存 static void deallocate(void *p,size_t n);//释放内存 static void* reallocate(void *p,size_t old_sz,size_t new_sz);//重新配置内存 }; /************以下只是初值的一个设定,客户端肯定得自己重新设置过的**************/ template<bool threads,int inst> char *_default_alloc_template<threads,inst>::start_free=0; template<bool threads,int inst> char *_default_alloc_template<threads,inst>::end_free=0; template<bool threads,int inst> size_t _default_alloc_template<threads,inst>::heap_size=0; template<bool threads,int inst> _default_alloc_template<threads,inst>::obj* volatile _default_alloc_template<threads,inst>::free_list[_NFREELISTS]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; /******************************暴露在外的函数实现******************************/ //作为第二级配置器的内存分配函数,需要先判断所申请内存大小是否大于128byte //如果小于128bytes就得检查对应的free list中是否有可用内存块 //如果没有,就需要调用refill(),为free list重新填充空间 template<bool threads,int inst> static void* _default_alloc_template<threads,inst>::allocate(size_t n) { //原来的声明形式是:obj** my_free_list,这样的话*my_free_list(空闲的内存块指针数组中的一个元素,指向一个空闲内存块)可能会被 //优化掉,为了不被优化掉,使用了volatile来提供对这些地址的稳定访问 obj *volatile *my_free_list; obj *mresult; if(n>(size_t)_MAX_BYTES)//大于128就直接调用一级配置器配置 { return (malloc_alloc::allocate()); } my_free_list=free_list+FREELIST_INDEX(n); mresult=*my_free_list; if(mresult==0)//如果没找到可用的free list,准备重新填充free list { void *r=refill(ROUND_UP(n)); return r; } else { //调整free list *my_free_list=mresult->free_list_link; return (mresult); } } template<bool threads,int inst> static void _default_alloc_template<threads,inst>::deallocate(void *p,size_t n) { obj *q=(obj*)p; obj *volatile *my_free_list; if(n>(size_t)_MAX_BYTES)//大于128就调用第一级配置器 { malloc_alloc::deallocate(p,n); return; } my_free_list=free_list+FREELIST_INDEX(n); q->free_list_link=*my_free_list; *my_free_list=q;//把这块内存回收到free list里 } /******************************功能函数的实现**********************************/ //如果free list中没有可用内存块了,就调用refill(),为free list填充空间。新的空间从内存池中取,内存池由chunk_alloc()完成 template<bool threads,int inst> void* _default_alloc_template<threads,inst>::refill(size_t n) { int nobjs=20; char *chunk=chunk_alloc(n,nobjs);//从内存池中取出nobjs个大小为n的内存块 obj *volatile *my_free_list; obj *mresult; obj *current_obj,*next_obj; int i; if(nobjs==1)//如果只获得一个区块,这个区块就分配给调用者用,free list无新节点 { return (chunk); }//否则准备调整free list,纳入新节点 my_free_list=free_list+FREELIST_INDEX(n); //以下在chunk空间内建立free list mresult=(obj*)chunk;//这一块儿准备返回客户端 //以下引导free list指向新配置的空间(取自内存池) *my_free_list=next_obj=(obj*)(chunk+n); //以下将free list的各节点串连起来 for(i=1;;i++)//因为0返回给客户端了,所以从1开始 { current_obj=next_obj; next_obj=(obj*)((char*)next_obj+n); if(nobjs-1==i) { current_obj->free_list_link=0; break; } else { current_obj->free_list_link=next_obj; } } return (mresult); } //内存池中没有空余区块了,就从其他的free list中找空余区块,加入到内存池中。如果其他free list //也没有空余区块了,那就调用一级配置器,使用out of memory template<bool threads,int inst> static char* _default_alloc_template<threads,inst>::chunk_alloc(size_t size,int &nobjs) { char *mresult; size_t total_bytes=size*nobjs; size_t bytes_left=end_free-start_free;//内存池剩余空间 if(bytes_left>=total_bytes) { mresult=start_free; start_free+=total_bytes; return(mresult); } else if(bytes_left>=size) { nobjs=bytes_left/size; total_bytes=size*nobjs; mresult=start_free; start_free+=total_bytes; return (mresult); } else//内存池连一个区块的大小都无法提供 { size_t bytes_to_get=2*total_bytes+ROUND_UP(heap_size>>4); //试着让内存池中的残余零头还有利用价值 if(bytes_left>0) { //内存池中的零头先配给适当的free list obj * volatile *my_free_list=free_list+FREELIST_INDEX(bytes_left);//寻找合适的freelist ((obj*)start_free)->free_list_link=*my_free_list; *my_free_list=(obj*)start_free; } //配置heap空间,用来补充内存池 start_free=(char*)malloc(bytes_to_get); if(start_free==0)//heap空间不足,malloc()失败 { int i; obj *volatile *my_free_list,*p; for(i=size;i<=_MAX_BYTES;i+=_ALIGN) { my_free_list=free_list+FREELIST_INDEX(i); p=*my_free_list; if(p!=0)//从其他的free list中摘下空余的区块,拆分,加入到内存池中 { *my_free_list=p->free_list_link; start_free=(char*)p; end_free=start_free+i; return (chunk_alloc(size,nobjs));//为修改nobjs,递归调用 } } //如果连其他所有的free list中也没有空余内存块了 end_free=0; //调用第一级配置器,看看out of memory机制是否还能尽点力 start_free=(char*)malloc_alloc::allocate(bytes_to_get); } heap_size+=bytes_to_get; end_free=start_free+bytes_to_get; return (chunk_alloc(size,nobjs));//递归调用自己,为了修正nobjs } }
stl5个内存的基本处理工具:在第二节具有次配置力的空间配置器中的stl_constructor.h中的用于构造的constructor和用于析构的destroy;还有就是uninitialized_copy()、uninitialized_fill()、uninitialized_fill_n(),分别对应高层次常用函数copy()、fill()、fill_n()。
/*******************************uninitialized_copy****************************** 如果作为输出目的地的[mresult,mresult+(last-first))范围内的每一个迭代器都只想未初始化区域, 则uninitialized_copy会使用copy constructor,给身为输入源的[first,last)范围内的每一个对象 产生一份复制品 *******************************************************************************/ template<class input_iterator,class forward_iterator> forward_iterator uninitialized_copy(input_iterator first,input_iterator last,forward_iterator mresult); /*******************************uninitialized_fill****************************** 如果[first,last)范围内的每个迭代器都指向未初始化的内存,那么uninitialized_fill会在该范围 内产生x的复制品。换句话说,uninitialized_fill会对每一个迭代器i,调用构造函数construct(&*i,x); *******************************************************************************/ template<class forward_iterator,class T> void uninitialized_fill(forward_iterator first,forward_iterator last,const T &x); /*******************************uninitialized_fill_n****************************** 如果[first,first+n)范围内的每个迭代器都指向未初始化的内存,那么uninitialized_fill会在该范围 内产生x的复制品。换句话说,uninitialized_fill会对每一个迭代器i,调用构造函数construct(&*i,x); *******************************************************************************/ template<class forward_iterator,class Size,class T> forward_iterator uninitialized_fill_n(forward_iterator first,Size n,const T &x);