美文网首页
STL第二级空间配置器风格的内存池实现

STL第二级空间配置器风格的内存池实现

作者: XDgbh | 来源:发表于2018-08-22 22:26 被阅读44次
    #include<malloc.h>
    #include<iostream>
    using namespace std;
    
    class memory_pool
    {
    private:
        static const size_t min_obj_size = 8;
        static const size_t max_obj_size = 128;
        char * start_free;  //内存池起始地址
        char * end_free;    //内存池结束地址
        size_t heap_size;   //记录从堆中分配到内存池的总数量
        union obj   //一个区块单元设计,有下一个区块链表和首地字节址共享
        {
            union obj * next_obj;
            char data;
        };
        obj * free_list[16];    //16种不同大小的区块的链表指针数组
    
        //可以设计成单例模式,只有一个内存池对象,私有构造
        memory_pool()
        {
            start_free = 0;
            end_free = 0;
        }
    
        ~memory_pool()
        {
            //释放成员函数申请的堆内存
        }
    public:
        //获取线程池类的单实例
        static memory_pool* getInstance()
        {
            static memory_pool instance;
            return &instance;
        }
    
        //将要申请的n字节升级到最近的8的倍数
        size_t up_to_8s(const size_t &n) const
        {
            return (n + min_obj_size - 1)&(~(min_obj_size - 1));    //(n+7)&11111000,即后3位清零为8的倍数
        }
    
        //根据n获取对应的链表位置
        size_t free_index(const size_t &n) const
        {
            return up_to_8s(n) / min_obj_size - 1;  //链表位置从0开始到15
        }
    
        //重点给用户调用的分配内存的函数,C风格只提供字节数
        void * allocate(const size_t &n)
        {
            //如果申请的内存大于最大区块,那么就直接从malloc获取
            if (n>max_obj_size)
            {
                return malloc(n);
            }
            //获取对应链表的首地址
            obj * list_head = free_list[free_index(n)];
            obj * result = list_head;
            if (NULL == result)
            {
                //若是没有获取到链表区块,则要重新填充该链表
                void * ret = refill(up_to_8s(n));
                return ret;
            }
            //更新链表首地址
            free_list[free_index(n)] = result->next_obj;
            return result;
        }
    
        //释放回内存给对应的链表
        void deallocate(void *p, const size_t n) 
        {
            //如果大于128,说明是从堆中分配的,要还给堆
            if (n>max_obj_size)
            {
                free(p);
            }
            obj * list_head = free_list[free_index(n)];
            obj * new_head = (obj*)p;
            //将原链表头作为新表头的下一个元素
            new_head->next_obj = list_head;
            //新表头放到链表首部位置
            free_list[free_index(n)] = new_head;
        }
    
        //重新从内存池中获取内存填充一个区块大小为n的链表
        void * refill(const size_t n)
        {
            int nobjs = 20; //默认一次获取20个相应区块
            //从内存池中获取这一整块n*nobjs的内存,传入的nobjs可能被修改为实际获取到的值
            char * chunk = chunk_alloc(n, nobjs);
            if (1 == nobjs)
            {
                //说明从内存池也只获取到了一个区块,直接返回给需要的
                return chunk;
            }
            //从内存池中获取了多个区块,则要重新链接起来
            obj * list_head = free_list[free_index(n)];
            obj *result = (obj*)chunk;
            obj *current_obj, *next_obj;
            free_list[free_index(n)] = 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->next_obj = NULL;
                    break;
                }
                else
                {
                    current_obj->next_obj = next_obj;
                }
            }
            return result;  //返回的是大区块首地址
        }
    
        //从内存池中获取大块内存
        char * chunk_alloc(const size_t n, int &nobjs)
        {
            char* result;
            size_t total_bytes = n*nobjs;
            size_t bytes_left = end_free - start_free;
            //如果内存池中剩余字节数能满足需求那就先分配
            if (bytes_left>=total_bytes)
            {
                result = start_free;    //返回结果就是内存池首地址
                start_free = start_free + total_bytes;  //内存池新的首地址
                return result;
            }
            //内存池中的剩余字节还够至少一个小区快
            else if (bytes_left>n)
            {
                nobjs = bytes_left / n; //能获取的区块个数
                total_bytes = n*nobjs;
                result = start_free;
                start_free = start_free + total_bytes;
                return result;
            }
            //一个小区快都不够,则要重新从堆中往内存池分一大块内存
            else
            {
                //需要从内存池中获取的内存是当前需求的两倍,以及加上从堆空间分配的总字节数相关的一个附加值
                size_t bytes_to_get = 2 * total_bytes + up_to_8s(heap_size >> 4);
                //先把内存池之前剩余的小部分分到合适的链表上
                if (bytes_left>0)
                {
                    deallocate(start_free, bytes_left); //回收到链表中
                }
                //重新从堆空间获取内存
                start_free = (char *)malloc(bytes_to_get);
                if (NULL == start_free)
                {
                    //说明堆空间都没有内存可分配了,则要从已分配的链表中找到最大的空闲区块分配过来
                    for (int i = max_obj_size / min_obj_size; i >= 0; i--)
                    {
                        obj* list_head = free_list[i];
                        if (NULL != list_head)
                        {
                            //说明找到了一个大区块
                            obj* p = list_head;
                            //将这个区块放到内存池
                            start_free = (char*)p;
                            end_free = start_free + (i + 1)*min_obj_size;
                            free_list[i] = p->next_obj;
                            return chunk_alloc(n, nobjs);   //重新调整内存池后递归调用
                        }
                    }
    
                    //说明所有地方都没有内存可取
                    end_free = start_free = 0;
                    cerr << "out of memory !" << endl;
                    exit(1);    //退出程序
                }
    
                heap_size += bytes_to_get;  //更新从堆中分配给内存池的总数量
                end_free = start_free + bytes_to_get;
                //内存池已更新,则需要递归调用次函数重新分配区块到链表
                return chunk_alloc(n, nobjs);
            }
        }
        
    };
    
    //测试代码
    int main()
    {
        memory_pool *pMpool = memory_pool::getInstance();
        double *d = (double*)pMpool->allocate(sizeof(double));
        *d = 3.1415926;
        cout << *d << endl;    //打印出 3.1415926
        pMpool->deallocate(d, sizeof(double));
    
        cout << "success!!!" << endl;    //打印出success
    }
    

    相关文章

      网友评论

          本文标题:STL第二级空间配置器风格的内存池实现

          本文链接:https://www.haomeiwen.com/subject/yemhiftx.html