zoukankan      html  css  js  c++  java
  • 空间适配器

      自定义一个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来复制。

  • 相关阅读:
    POST和GET的区别
    Java设计模式6大原则
    JAVA23种工厂模式
    使用jsp实现用户登录请求
    MVC模式
    使用idea查询数据库内容
    mysql常见错误
    定义外键和建表原则
    CSS制作圆角边框
    2、JS的编写位置
  • 原文地址:https://www.cnblogs.com/tianzeng/p/12547217.html
Copyright © 2011-2022 走看看