空间适配器

  自定义一个allocate

#ifndef JJALLOC_H_INCLUDED
#define JJALLOC_H_INCLUDED
#include<new>
#include<cstddef>//定义了一些标准宏及类型。
#include<cstdlib>//常用函数库
#include<climits>//定义数据类型的最大最小值
#include<iostream>//输入输出流

namespace JJ
{
    //配置模板类空间的函数,传入数据大小,申请内存
    template<class T>
    inline T* _allocate(ptrdiff_t size,T*)
        //ptrdiff_t是C/C++标准库中定义的一个与机器相关的数据类型。ptrdiff_t类型变量通常用来保存两个指针减法操作的结果。
    {
        std::set_new_handler(nullptr);
        //set_new_handler(0)主要是为了卸载目前的内存分配异常处理函数,这样一来一旦分配内存失败的话,C++就会强制性抛出std:bad_alloc异常,而不是跑到处理某个异常处理函数去处理
        T *tmp=(T*)(::operator new((size_t)(size *sizeof(T))));
        //::operator new()这是一个重载函数,完成的操作一般只是分配内存,事实上系统默认的全局::operator new(size_t size)也只是调用malloc分配内存,并且返回一个void*指针。而构造函数的调用(如果需要)是在new运算符中完成的。
        if(tmp==nullptr)
        {
            std::cerr<<"内存超出"<<std::endl;
            exit(1);
        }
        return tmp;
    }

    //归还先前配置的空间
    template<class T>
    inline void _deallocate(T* buffer)
    {
        ::operator delete(buffer);
        //::operator delete()与operator new相反,释放内存。
    }

    //通过传递目标地址,和目的内容,构造出目标对象。
    template<class T1,class T2>
    inline void _construct(T1 *p,const T2& value)
    {
        new(p) T1(value);
        //placement new,在指针p所指向的内存空间创建一个类型为T1的对象。
    }

    //摧毁:调用析构函数
    template<class T>
    inline void _destroy(T* ptr)
    {
        ptr->~T();
    }

    template<class T>
    class allocator
    {
    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
        {
            //T这个类里,可以存储着U类对象
            typedef allocator<U> other;
        };

        //申请内存
        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 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(T));
        }
    };
}

#endif // JJALLOC_H_INCLUDED

SGI标准的空间适配器

  在<memory>包含以下两个头文件

#include<stl_alloc.h>//负责内存的配置和释放
#include<stl_construct.h>//负责对象的构造和析构

  它们的调用关系是

构造和析构工具:constructor、destructor

#ifndef __SGI_STL_INTERNAL_CONSTRUCT_H
#define __SGI_STL_INTERNAL_CONSTRUCT_H

#include <new.h>

__STL_BEGIN_NAMESPACE

template <class T>//析构单个元素
inline void destroy(T* pointer) {
    pointer->~T();
}

template <class T1, class T2>
inline void construct(T1* p, const T2& value) {
  new (p) T1(value);    //布局new(placement new) 在p地址处调用T1构造函数构造对象
}

template <class ForwardIterator>
inline void
__destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) {
  for ( ; first < last; ++first)//如果元素的析构函数是必要的 那么逐个调用析构函数
    destroy(&*first);
}

template <class ForwardIterator> //如果元素的析构函数是无关紧要的  就什么也不做
inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) {}

template <class ForwardIterator, class T>
inline void __destroy(ForwardIterator first, ForwardIterator last, T*) {
  //通过元素型别来判断析构函数是否无关紧要(trivial) 并调用对应的函数进行析构
  typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;
  __destroy_aux(first, last, trivial_destructor());
}

template <class ForwardIterator>
inline void destroy(ForwardIterator first, ForwardIterator last) {
  __destroy(first, last, value_type(first));//通过泛型的类型识别技术来得到元素类型
}

inline void destroy(char*, char*) {}
inline void destroy(wchar_t*, wchar_t*) {}

__STL_END_NAMESPACE

#endif /* __SGI_STL_INTERNAL_CONSTRUCT_H */

  constructor接受一个指针和一个初始值value。

  destroy有两个版本:

  1. 接受一个指针,将指针所指之物析构掉,直接调用析构函数即可。
  2. 接受两个迭代器,因为不确定迭代器的范围,所以为了节约开销,先用value_type()获取迭代器所指对象的型别,再用__type_traits<T>判断该型别是否是无用的,如是则就什么也不做,否则访问每一个对象对其调用析构函数。

  以上描述的是内存购置后对象的初始化与释放行为。

空间的配置与释放

  1. 向system heap要求空间
  2. 考虑多线程状态
  3. 考虑内存不足时的应变措施
  4. 考虑内存碎片问题

  SGI采用双层配置器,第一级配置器直接采用malloc和free;第二级配置器视情况不同采用不同策略,当配置区块超过128B时,调用一级配置器,小于时用内存池。设计是否同时开启两级配置器取决于_USE_MALLC的定义。

 

第一级适配器__malloc_alloc_tmplate

#if 0
#include <new>
#define __THROW_BAD_ALLOC throw bad_alloc
#elif !defined(__THROW_BAD_ALLOC)
#include <iostream>
#include <stdlib.h>
#define __THROW_BAD_ALLOC std::cerr<<"out of memory"<<std::endl; exit(1)
#endif

//第一级空间配置器
template<int inst>
class __malloc_alloc_template
{
private:
    //处理内存不足的情况
    //oom:  out of memory
    
    //当内存不足时,申请内存
    static void *oom_malloc(size_t);
    //当内存不足时,重新规划内存
    static void *oom_realloc(void *, size_t);
    //oom下尝试释放内存的例程
    static void (* __malloc_alloc_oom_handler)();

public:
    //配置内存
    static void * allocate(size_t n)
    {
        //直接调用malloc
        void *result = malloc(n);   //第一级配置器直接使用malloc()
        //无法满足需求时,直接使用oom_malloc()
        if(0 == result)
            result = oom_malloc(n);
        return result;
    }
    //释放内存
    static void deallocate(void *p, size_t )
    {
        //这里直接调用free
        free(p);
    }
    //重新规划内存
    static void *reallocate(void *p, size_t , size_t new_sz)
    {
        //直接调用realloc
        void * result = realloc(p, new_sz);
        //无法满足需求时,直接使用oom_realloc()
        if(0 == result)
            result = oom_realloc(p, new_sz);
        return result;
    }
    //模仿std::set_new_handler();可以在这里定义自己oom句柄
    static void (*set_malloc_handler(void (*f)()))()//函数返回值为函数指针,参数是void (*f)(),即函数指针
    {
        void (*old)() = __malloc_alloc_oom_handler;
        __malloc_alloc_oom_handler = f;
        return (old);
    }
};

//malloc_alloc out_of_memory handling
//将指针指向的地址为0
template<int inst>
void (*__malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0;

//oom下申请内存
template <int inst>
void *__malloc_alloc_template<inst>::oom_malloc(size_t n)
{
    void (*my_malloc_handler)();
    void *result;
    //不断尝试释放,配置,再释放,再配置
    for(;;)         
    {
        my_malloc_handler = __malloc_alloc_oom_handler;
        if(0 == my_malloc_handler)
        {
            __THROW_BAD_ALLOC;
        }
        (*my_malloc_handler)();//企图释放内存
        result = malloc(n);
        if(result)
            return result;
    }
}

//oom下重新规划内存
template<int inst>
void * __malloc_alloc_template<inst>::oom_realloc(void *p, size_t n)
{
    void (*my_malloc_handler)();
    void *result;
    //与oom_malloc类似,不断尝试释放,配置,再释放,再配置
    for(;;)
    {
        my_malloc_handler == __malloc_alloc_oom_handler;
        if(0 == my_malloc_handler)
        {
            __THROW_BAD_ALLOC;
        }
        (*my_malloc_handler)();//企图释放内存
        result = realloc(p, n);
        if(result)
            return result;
    }
}
//这里直接将inst指定为0了
typedef __malloc_alloc_template<0> malloc_alloc;

  第一级适配器使用malloc,realloc,free来分配内存,当系统内存无法被满足时,调用类似于c++ new handler来分配内存,也就是::operator new无法分配内存时,在丢出std::bad_malloc之前调用new headler,如果客户端未设定new-handler,则会直接抛异常退出程序。

第二级适配器__default_alloc_template

  二级配置器维护着16个自由链表,他们维护的内存大小分别是8,16,24到128,其结构如下

union obj
{
    union obj* free_list_link;
    char client_data[1];
};

  由于union的缘故,obj可视为一个指针,指向与第一个形式相同的obj,从第二个字段看,obj也可视为一个实际所指的内存区,如此可节省存储额外空间的指针。

 

template <bool threads, int inst>
class __default_alloc_template {

private:

//区块上调枚举
#if ! (defined(__SUNPRO_CC) || defined(__GNUC__))
    enum {_ALIGN = 8};
    enum {_MAX_BYTES = 128};
    enum {_NFREELISTS = 16}; // _MAX_BYTES/_ALIGN
# endif

private:
//区块上调函数,接收到申请的大小,我们向上取8的倍数
  static size_t    ROUND_UP(size_t __bytes) 
    { return (((__bytes) + (size_t) _ALIGN-1) & ~((size_t) _ALIGN - 1)); }

private:
//链表节点联合体
  union _Obj {
        union _Obj* _M_free_list_link;
        char _M_client_data[1];    /* The client sees this.*/
  };
  
private:
# if defined(__SUNPRO_CC) || defined(__GNUC__) || defined(__HP_aCC)
    static _Obj* __STL_VOLATILE _S_free_list[]; 
# else
    //16个 free-list
    static _Obj* __STL_VOLATILE _S_free_list[_NFREELISTS]; 
# endif
    //根据申请的大小,确定出在自由链表的下标
  static  size_t FREELIST_INDEX(size_t __bytes) {
        return (((__bytes) + (size_t)_ALIGN-1)/(size_t)_ALIGN - 1);
  }
  //返回大小为__n的对象
  static void* refill(size_t __n);
  //配置一大块空间,可以容纳__nobjs大小个__size的区块
  //可能会随着内存不足,而降低__nobjs数量
  static char* chunk_alloc(size_t __size, int& __nobjs);
  static char* start_free;        //内存池起始
  static char* end_free;          //内存池结束
  static size_t heap_size;        //区块数
  
public:
  /* __n must be > 0      */
  static void* allocate(size_t __n)
  {
    ...
  };

  /* __p may not be 0 */
  static void deallocate(void* __p, size_t __n)
  {
    ...
  }

  static void* reallocate(void* __p, size_t __old_sz, size_t __new_sz);

};

//这四个函数是静态数据成员的定义与初始设定
//__threads是线程设定,书中不讨论
template <bool __threads, int __inst>
char* __default_alloc_template<__threads, __inst>::_S_start_free = 0;

template <bool __threads, int __inst>
char* __default_alloc_template<__threads, __inst>::_S_end_free = 0;

template <bool __threads, int __inst>
size_t __default_alloc_template<__threads, __inst>::_S_heap_size = 0;

template <bool __threads, int __inst>
typename __default_alloc_template<__threads, __inst>::_Obj* __STL_VOLATILE
__default_alloc_template<__threads, __inst> ::_S_free_list[
# if defined(__SUNPRO_CC) || defined(__GNUC__) || defined(__HP_aCC)
    _NFREELISTS
# else
    __default_alloc_template<__threads, __inst>::_NFREELISTS
# endif
] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
};

空间配置函数allocate

  作为一个空间配置器,自然拥有配置器的标准接口函数allocate(),此函数首先判断区块大小,大于128字节就调用一级配置器,小于128字节就检查对应的自由链表,如果对应链表可用,那么久拿来用,反之,将区块大小上调至8倍数边界,然后调用refill(),重新填充空间。

static  void * allocate(size_t n)
{
    obj *volatile * my_free_list;
    obj *result;
    //如果申请大小n大于128,直接调用一级配置器
    if(n>(size_t)__MAX_BYTES)
    {
        //调用一级配置器的空间配置函数。
        return (malloc_alloc::allocate(n));
    }
    //通过大小确定自由链表的对应下标
    my_free_list=free_list+FREELIST_INDEX(n);
    result=*my_free_list;
    //如果返回为空
    if(result==0)
    {
        void *r =refill(ROUND_UP(n));
        return r;
    }
    //对自由链表进行调整
    *my_free_list=result->free_list_link;
    return (result);
}

空间释放函数deallocate

  作为一个空间配置器,自然拥有配置器的标准接口函数deallocate(),此函数首先判断区块大小,大于128字节就调用一级配置器,小于128字节就检查对应的自由链表,回收空间。

static  void * deallocate(void *p,size_t n)
{
    obj *q=(obj *)p;
    obj * volatile * my_free_list;
    
    //如果申请大小n大于128,直接调用一级配置器
    if(n>(size_t)__MAX_BYTES)
    {
        //调用一级配置器的空间配置函数。
        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 lists

  回头讨论先前说过的allocate()。当它发现自由链表中没有可用区块时,会调用refill它会将chunk_alloc输出的空间维护成自由链表。缺省取得20个新区块,如果内存池空间不足,获得区块数可能小于20.

template <bool threads,int inst>
void * __default_alloc_template<threads,inst>::refill(size_t n)
{
    int nobjs=20;
    //调用chunk_alloc(),尝试取得nobjs个区块作为自由链表的新节点。
    //传参nobjs是个引用
    char *chunk=chunk_alloc(n,nobjs);
    //自由链表
    obj *volatile * my_free_list;
    obj *result;
    //用于连接的前后节点
    obj * current_obj,*next_obj;
    int i;
    //如果当前需求只有一个,我们直接返回给调用者,不用添加到自由链表里
    if(nobjs==1)
        return (chunk);
    //取得目的自由链表
    my_free_list=free_list+FREELIST_INDEX(n);
    //显示转换,将result指向新的来的区块头部,后面返回给调用者
    result=(obj *)chunk;
    //chunk+n 是指chunk后移n位,就是下一块区块
    *my_free_list=next_obj=(obj *)(chunk +n);
    for(i =1;;i++)
    {
        current_obj=next_obj;
        //取得下一区块
        next_obj=(obj*)((char *)next_obj+n);
        //当连接数量满足时,跳出
        if(nobjs-1==i)
        {
            //把尾部指向0/nullptr
            current_obj->free_list_link=0;
            break;
        }
        else
        {
            current_onj->free_list_link=next_obj;
        }
    }
    return(result);
}

内存池

  从内存池中取出空间给free lists,主要是chunk_alloc,它是向堆申请空间给内存池

template <bool threads,int inst>
char * __default_alloc_template<threads,inst>::chunk_alloc(size_t size,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;
        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 
    {
        //这里获得要使用malloc申请的内存大小,建议申请的大小是当前需求的两倍加上ROUND_UP(heap_size>>4),不过目前无法理解右移4位的意思
        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;
        }
        //使用malloc申请大小
        start_free=(char *)malloc(bytes_to_get);
        //申请失败
        if(start_free==0)
        {
            int i;
            obj * volatile * my_free_list,*p;
            //每次i+8
             for (i = size;i <= (size_t) __MAX_BYTES;i += (size_t) __ALIGN) 
             {
                 //从下标0到15,检索所有自由链表
                my_free_list = free_list + FREELIST_INDEX(i);
                p = *my_free_list;
                 //如果某个链表还有区块
                if (0 != p) {
                    //我们取一块区块出来
                    *my_free_list = p -> free_list_link;
                    //把这块当作新的内存池可用空间
                    start_free = (char*)p;
                    end_free = start_free + i; 
                    //再调用自身,看看这个区块的内存是否足够
                    return(chunk_alloc(size, nobjs));
                }
            }
            end_free = 0;   
            //调用一级配置器,看看oom下是否可以尽力完成
            start_free = (char*)malloc_alloc::allocate(bytes_to_get);
        }
        heap_size += bytes_to_get;
        end_free = start_free + bytes_to_get;
        //递归自己,修正nobjs
        return(chunk_alloc(size, nobjs));
        }
    }
}

  通过end_free和start_free来判断内存池的容量是否充足,如果充足就把20个块分配出去,如果不够20个快的容量但是够一个以上的块的容量,就把能分配实际的块容量分配出,修改nobjs,如果一个块的容量都不够,就通过malloc向heap申请空间,申请的数量为需求量的两倍

工作原理:

  假设程序一开始,客端就调用chunk_alloc(32,20),对应的free_list[3]空空如也,此时内存池为空,于是malloc()配置40个32字节的区块。并把第一个交给调用者,剩下19个交给free_list[3]维护,剩下20个交给内存池。客端接着调用chunk_alloc(64,20),对应的free_list[7]空空如也,内存池是20*32字节大小的连续内存,可以分成10块64字节大小的区块,把第一块返回给调用者,剩下9块加入到对应的自由链表free_list[7],此时内存池为空。接下来调用chunk_alloc(96,20),free_list[11]为空,必须要求内存池支持,于是malloc配置40+n(附加量)个96B的区块,交出第一个,剩下19个给free_list,剩下20个给内存池。

  若果system heap无可用空间,chunk_alloc就找free_list中尚未使用的且足够大的块,找到就挖出该块,找不到就调用一级配置器,一级配置器也是调用malloc,但是他有new-handler机制,可能释放其他空间来满足需要。

总的来讲:

  分配内存的空间如果大于128B,就调用一级适配器;否则调用二级适配器,第一次调用二级适配器时内存池容量为空,会调用malloc申请需求2倍的空间,其中一半留给内存池下次分配使用,在此分配时,如果调整后对应的free_list为空则就相内存池申请,如果向内存池申请(malloc)失败则会扫描free_list寻找未用的空闲大块,若找到分配,否则调用一级适配器malloc,但是一级适配器有new-handler机制可能会释放其他空间来满足申请。

基本内存处理工具

  uninitialized_copy,uninitialized_fill,uninitialized_fill_n都是在配置的区块上构造元素,他们都有一个共同的语意:要么构造成所有元素要么一个元素都不构造。如果其中一个复制构造函数抛异常那么会释放其他所有已构造元素。

  两种特化char*和wchar_t*才用移动赋值memmove来复制。

posted on 2020-03-22 17:36  tianzeng  阅读(313)  评论(0编辑  收藏  举报

导航