STL空间配置器源码及其分析

来源:互联网 发布:怎样抢注域名 编辑:程序博客网 时间:2024/04/30 15:21

#ifndef _STLALLOC_H_

#define _STLALLOC_H_

#include <typeinfo>

#include <new>   //placement new

#include <iostream>

#include <malloc.h>

//#include <>

#include <stdlib.h>

using namespace std;

 

#if 0   //为何如此定义?

#   include <new>

#   define __THROW_BAD_ALLOC_

#elif !defined(__THROW_BAD_ALLOC_)

#   define __THROW_BAD_ALLOC_  cerr<< "out of memory !" << endl; exit(1);

#endif

template <int inst>  //非型别参数,完全没有派上用场

class __malloc_alloc_template

{

private:

//处理内存不足函数:

static void *oom_malloc(size_t);

static void *oom_remalloc(void *,size_t);

static void (*__malloc_oom_handler)(); //void(*)()  malloc_oom_handler 想当于定义了一个函数指针

public:

static void *allocate(size_t n)

{

   void *result = malloc(n);//一级配置器直接使用malloc(),如果无法满足需求,则调用oom_malloc()(循环)

   if(0== result) result = oom_malloc(n);

   return result;

}

//一级配置器直接使用free

static void deallocate(void *p,size_t)

{

   free(p);

}

static void *reallocate(void *p, size_t ,size_t new_sz)

{

   void *result = realloc(p, new_sz);

   if(0 == result) result = oom_remalloc(p, new_sz);

   return result;

}

//此做法类似于C++的set_new_handler处理机制,f代表客户可以自己定义内存不足的处理函数set_new_handler是一个函数,函数的参数为函数指针(void(*)()),返回值为函数指针(void(*)())

static void(*set_malloc_handler(void(*f)()))()

{

   void(*old)() = __malloc_oom_handler; //用一个函数指针先保存先前的内存处理函数

   __malloc_oom_handler = f; //让私有成员的数据指针指向f(传进来的函数名)即client自己定义的内存不足函数处理函数

   return (old);

}

//此函数的作用:(1)函数参数为f 如果客户没有定义自己的处理函数,即此时系统默认的f == NULL;返回值为空,则在循环体中就会调用系统的默认的处理机制(__THROW_BAD_ALLOC_)

         (2)此函数返回注册的前异常处理函数,以便前面的处理函数值后还有可能返回;

};

template <int inst>

void(*__malloc_alloc_template<inst>:: __malloc_oom_handler)() = 0;//内存不足处理函数的初值设置为0,需要客户自己定义如果客户未定义,则会调用系统默认的处理机制

template<int inst>

void*__malloc_alloc_template<inst>::oom_malloc(size_t n)

{

   void(*my_malloc_handler)();//定义一个函数指针

    void *result;

   for(;;)

    {

       my_malloc_handler = __malloc_oom_handler;//指针指向一个中间桥梁的函数指针(类的私有成员)

       if(0 == my_malloc_handler) //即如果客户没有设置自己的内存不足处理函数,系统就会调用自身的处理函数

       {

           __THROW_BAD_ALLOC_;

       }

       (*my_malloc_handler)();//如果有,则执行内存客户自定义的内存不足处理函数

       result = oom_malloc(n);

       if(result) return result;

    }

}

template<int inst>

void*__malloc_alloc_template<inst>::oom_remalloc(void *p , size_t n)

{

   void(*my_malloc_handler)();

   void *result;

   for(;;)

    {

       my_malloc_handler = __malloc_oom_handler;

       if(0 == my_malloc_handler)

       {

           __THROW_BAD_ALLOC_;

       }

       (*my_malloc_handler)();

       result = oom_remalloc(p,n);

       if(result) return result;

    }

}

//**************************************************************

//**************************************************************

enum{__ALIGN = 8};//小型区块的上调边界(align 匹配,结盟)

enum{__MAX_BYTES = 128};//最大字节

enum{__NFREELISTS = __MAX_BYTES/__ALIGN};//free lists 的个数

 

//二级配置器无模板参数,且其类型没有作用

template<int inst>

class __default_alloc_template

{

private:

   //将字节上调至8的倍数(round up 集成整数)

   static size_t ROUND_UP(size_t bytes)

    {

       return (((bytes) + __ALIGN - 1) & ~(__ALIGN - 1));

    }

private:

   //*****************************************************************************************

   //free lists 的构造节点

   union obj

    {

       union obj *free_list_link;

       char client_data[1];

   };

   //此设计极为巧妙,对空间的怜惜可见一斑,对空间的分配极为吝啬,结构体占四个字节,当申请到空间时,将申请到的一部分[刚开始(19个)最为自由链表,一个返回,给调用的对象用,剩余的20个作为内存池]用这种形式的节点指针来维护链表,此结构形式极其象柔性数组,相当于用联合体的结构形式,把一个数组一部分链接起来,当第二次还有对象要用此节点时,此时,相当于链表的Popfront先用一个临时指针指向第一块内存,然后链表头指向它的下一块空间,最后将此临时指针返回,供调用对象使用.

   //****************************************************************************

private:

   //16个自由链表

   static obj *volatile free_list[__NFREELISTS];

   //volatile 是一个指令关键字,确保本条指令不会因为不会被编译器的优化而省略,且要求每次直接读值

   //****************************************************************************

   //根据区块的大小定位使用第几号自由链表

   static size_t FREELIST_INDEX(size_t bytes)

    {

       return (((bytes) + __ALIGN -1)/__ALIGN - 1);

    }

   //****************************************************************************

   static void *refill(size_t n);

   //返回大小为n 的区块,并可能加入大小为n 的其他区块到freelist.refill()重新填充自由链表,比如刚开始16个自由链表都为0,加入定位了5 个字节,则根据ROUND_UP()则上调至8个字节,然后根据定位函数FREELIST_INDEX()定位到0,定位到0号链表,但刚开始,0 号链表为空,此时要refill(),填充规则刚开始一个自由链表都没有u,则调用chunk_alloc() 函数此时默认申请的大小2 * size(8) * 20 + 一个变化的数(可以不用考虑)。== 320 bytes 将一个返回,19个链成自由链表,剩下的20个作为内存池,

   //****************************************************************************

   static char *chunk_alloc(size_t size, int& nobjs);

   //配置一块大空间,可容纳nobjs个大小为“size”的区块,如果配置nobjs 个大小的区块有所不便,nobjs的大小可能会降低,注意此处是引用,上面函数出来后,可能会改变nobjs的实际大小。

   //****************************************************************************

   static char *start_free;

   static char   *end_free;

   static size_t heap_size;

   //块的分配状态:用start_freeend_free 表示内存池的空间剩余状况,两者都是指针,相当于一个指向数组的没有存数据的起始,一个指向末尾。heap_size 主要是负责refill()函数中开辟空间时的变化的基数,注意此处的三个指针都声明为静态的原因?

public:

   static void *allocate(size_t n);            //

   static void deallocate(void *p, size_t n); //空间的释放

   static void *reallocate(void *p, size_t old_size,size_t new_sz);//

};

//****************************************************************************

//以下是类中的静态数据成员的定义与初始值设置

template<int inst>

char*__default_alloc_template<inst>::start_free = 0;

template<int inst>

char*__default_alloc_template<inst>::end_free   = 0;

template<int inst>

size_t __default_alloc_template<inst>::heap_size= 0;

 

template<int inst>

typename__default_alloc_template<inst>::obj *volatile   //整行都是返回值,因为obj是在一个不同参数的返回值,所以要声明其解析符

__default_alloc_template<inst>::free_list[__NFREELISTS]=

{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};   //初始16个自由链表,其内部的指向都为空

//****************************************************************************

template<int inst>

void*__default_alloc_template<inst>::allocate(size_t n)  //此处n必须必须大于0

{

   obj * volatile *my_free_list;

   //cout << "size_t : " << n << endl;

   obj *result; //定义此指针的目的是指向申请到的,要返回的空间,并将其返回如果申请的字节大于128字节,此时就调用一级空间配置器

   if(n > (size_t)__MAX_BYTES)

    {

       return(__malloc_alloc_template<0>::allocate(n));

    }

   //指针指向16个链表中合适的一个

   my_free_list = free_list + FREELIST_INDEX(n);//此时的my_free_list是二级指针,定位的是下标.

   result = *my_free_list;

   if(0 == result)//如果定位到的链表为空,则填充申请空间,挂载链表,返回申请到的空间

    {

       void * r = refill(ROUND_UP(n));

       return r;

    }

   *my_free_list = result->free_list_link;//如果定位的链表有空间,此时重新Popfront第一和节点

    return (result);                     //并返回。

}

//allocate()是二级空间配置器__default_malloc_template 的标准接口,此函数的作用是:首先先判断区块的大小,如果大于128则调用一级配置器,否则就检查对应对自由链表,如果自由链表有可用的区块,则就直接拿来用,如果没有区块,则将区块的大小上调至8的倍数,然后调用refill(),准备为free list重新填充。

//****************************************************************************

template<int inst>

//注意此处的n 是为二级空间配置器的释放所设计的,当大于128 bytes时,直接调用一级空间配置器的释放函数free(p),将对象释放掉,如果小于128 字节,则就不是‘释放’,而是先传入指针所指的对象,并且传入要‘释放’的块的大小。这样,最后释放的时候,其实是将传入的指针所指的块挂载到自由链表中。

void__default_alloc_template<inst>::deallocate(void *p ,size_t n)

{

   //cout << "3" << endl;

   obj *q = (obj*)p;

   obj* volatile *my_free_list;

   //cout << n << endl;

   //如果大于128字节,就交给一级配置器处理

   if(n > (size_t)__MAX_BYTES)

    {

       //cout << "error" << endl;

       __malloc_alloc_template<0>::deallocate(p,n);

    }

   //否则先定位

   my_free_list = free_list + FREELIST_INDEX(n);

   //将对象的空间pushfront到自由链表中,因为二级空间配置器,并不是释放空间,而是将空间挂载到自由链表上.(后话:当有对象申请时,此时先定为链表所在的下标,然后将链表上的一块空间用一个临时指针指向然后从自有链表释放,返回给对象临时指针所指的内容.)

   q->free_list_link = *my_free_list;

   *my_free_list   =  q;

}

//*****************************************************************************

template<int inst>

void*__default_alloc_template<inst>::refill(size_t n)

{

   int nobjs = 20; //默认取的20个新的节点区块,给自由链表填充

   //调用chunk_alloc(),尝试取的nobjs个区块作为free list 的新节点

   //此处注意nobjs 是引用传值

   char *chunk = chunk_alloc(n,nobjs);

   //cout << "nobjs = " << nobjs << endl;

   obj * volatile *my_free_list; //所有函数中的这种定义都是为了定位自由链表的下标

   obj *result; //所有的函数中这种定义都是为了给对象返回result指针所指的空间

   obj * current_obj, *next_obj;  //为了把申请的空间链成自由链表

//如果只获得一个区块,这个区块就分配给调用者使用,啥也不用做,自由链表没有新的节点

   if(1 == nobjs)

    {

       return chunk;

    }

   //否则,大于等于1个区块,此时要调整自由链表,纳入新的节点

   //(1)先定位:

   my_free_list = free_list + FREELIST_INDEX(n);

   //(2)让result指向申请的块的第一块内存位置,将其准备返还给客户端

   result = (obj *)chunk;

   //(3)引导除去第一块外chunk的空间,链成自由链表(注意:此处的空间是取自内存池)

   *my_free_list = next_obj = (obj *)(chunk + n);

   for(int i = 1; ;++i)

    {

       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 (result);

}

//*****************************************************************************

template<int inst>

char *

__default_alloc_template<inst>::chunk_alloc(size_tsize, int &nobjs)

{

   char *result;

   size_t total_bytes = size * nobjs;

   size_t bytes_left = end_free -  start_free;//高地址在下面

   //如果内存池剩余空间完全大于需求的字节

   if(bytes_left > total_bytes)

    {

       result = start_free;

       start_free += total_bytes; //因为要将total_bytes 的空间分配出去(给自由链表填充),所以要调整内存池的剩余空间,end_free指针指向一个中间桥梁的函数指针永远不会变.

       return result;

   //如果内存池剩余空间不能完全满足需求量,但足够供应一个(含一个以上)的空间

   }else if(bytes_left >= size)

    {

       nobjs = bytes_left /size;

       total_bytes = size * nobjs;

       result = start_free;

       start_free += total_bytes;

       return (result);

   //内存池剩余的空间连一个区块的大小都不能提供

   }else

    {

       size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);//需要申请的空间大小此时,对于剩余的残余空间,将其挂载在自身的自由链表上,让其最后有价值(如果堆上申请不到所需的大块内存,此时就返回来遍历一下,比自己'大'的链表,看看其是否还有尚未用的内存块)

       if(bytes_left > 0)

       {

           obj *volatile *my_free_list = free_list + FREELIST_INDEX(bytes_left);

           ((obj*)start_free)->free_list_link = *my_free_list;

           *my_free_list = (obj*)start_free;

       }

       //配置heap空间,补充内存池

       start_free = (char *)malloc(bytes_to_get);

       //如果堆上的空间都不足

       if(0 == start_free)

       {

            int i = 0;

            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;

                 //此free list 尚有未用的区块

                 if(0 != p)

                 {

                     //调整free lists 释放该区块

                     *my_free_list =p->free_list_link;

                     start_free = (char *)p;

                     end_free   = start_free + i;

                     return (chunk_alloc(size,nobjs));

                     //任何残余的零头,终将被编入适当的free lists 中,备用。

                 }

            }

            end_free = 0; //如果出现意外?(到处都没能找到内存可用)

    //调用一级配置器,看看out_of_memory机制能否尽力

start_free = (char*)__malloc_alloc_template<0>::allocate(bytes_to_get);

    //这会抛出异常(exception),或内存不足的情况

       }

       heap_size += bytes_to_get;

       end_free = start_free + bytes_to_get;

       //递归调用自己,为了修改nobjs

       return (chunk_alloc(size,nobjs));

    }

}

typedef __default_alloc_template<0>alloc;

#endif

//****************************************************************************

//如果之前定义了__USE_MALLOC这个标签,则就把一级空间配置器定义为alloc

#ifdef __USE_MALLOC

typedef __malloc_alloc_template<0>malloc_alloc;

typedef malloc_alloc alloc;

//如果之前没有定义标签,那么就把二级空间配置器定义为alloc

#else

typedef __default_alloc_template<0>alloc;

#endif

//我之前没有定义__USE_MALLOC 这个标签,所以我把二级空间配置器,定义为alloc接口空间分配器,

//SGI 的空间配置器也是这样做的,开放的是二级空间配置器,然后在最外层包装了一个接口

//simple_alloc

//*****************************************************************************

//SGI 全部使用simple_alloc这个接口:其内部的函数都全部转调用一级二级配置器的函数

template<typename T, typename Alloc>

class simple_alloc

{

public:

   static T*allocate(size_t n)

    {

       return 0 == n ? 0:(T*)Alloc::allocate(n * sizeof(T));

    }

   static T*allocate(void)

    {

       return (T*)Alloc::allocate(sizeof(T));

    }

   static void deallocate(T *p, size_t n)

    {

       if(0 != n)

       {

           Alloc::deallocate(p, n* sizeof(T));

       }

    }

   static void deallocate(T* p)

    {

       //测试:

       //cout << "typeid(T).name()" << typeid(T).name()<< endl;  //要查看模板的类型必须包含<typeinfo>

       Alloc::deallocate(p,sizeof(T));

    }

};

//****************************************************************************

 

0 0