C++内存池的实现和原理(二)

余生颓废 提交于 2020-07-28 17:27:55

 

 

 1、#include "Alloctor.h" 文件

#ifndef _ALLOCTOR_H_
#define _ALLOCTOR_H_

//重载函数声明
void* operator new(size_t size);
void operator delete(void* p);
void* operator new[](size_t size);
void operator delete[](void* p);

//给重载后的operator函数换一个函数名
void* mem_alloc(size_t size);
void mem_free(void* p);

#endif // !_ALLOCTOR_H_

 2、#include "Alloctor.cpp" 文件

 

#include"Alloctor.h"
#include<stdlib.h>
#include"MemoryMgr.hpp"

//重载new运算符,单个内存分配
void* operator new(size_t nSize)
{
    return MemoryMgr::Instance().allocMem(nSize);
}
//重载delete运算符,单个内存释放
void operator delete(void* p)
{
    MemoryMgr::Instance().freeMem(p);
}
//重载new运算符,多个内存分配
void* operator new[](size_t nSize)
{
    return MemoryMgr::Instance().allocMem(nSize);
}
//重载delete运算符,多个内存释放
void operator delete[](void* p)
{
    MemoryMgr::Instance().freeMem(p);
}

//给重载后的new和delete

 

 

 

 

3、#include "MemoryMgr.hpp"文件

#ifndef _MemoryMgr_hpp_
#define _MemoryMgr_hpp_
#include<stdlib.h>
#include<mutex>
#include<assert.h>//断言

//定义在Debug模式下输出调试信息
#ifdef _DEBUG
#include<stdio.h>
#define xPrintf(...) printf(__VA_ARGS__)
#else
#define xPrintf(...)
#endif // _DEBUG

//一个内存块的最大内存大小
#define MAX_MEMORY_SZIE 1024

//提前声明MemoryAlloc类
class MemoryAlloc;
//内存块描述信息                           
class MemoryBlock
{
public:
    //所属内存池
    MemoryAlloc* pAlloc;
    //下一块位置
    MemoryBlock* pNext;
    //内存块编号
    int nID;
    //引用次数
    int nRef;
    //是否在内存池中
    bool pbool;
private:
    //预留
    char c1;
    char c2;
    char c3;
};//32位系统中指针大小位4个字节,64位为8个字节
//windows系统中得内存分配为8的整数倍个字节,所有这里一个内存块的大小为32个字节
//const int a = sizeof(MemoryBlock);

//内存池
class MemoryAlloc
{
public:
    MemoryAlloc()
    {
        _pBuf = nullptr;
        _pHeader = nullptr;
        _nSize = 0;
        _nBlock = 0;
        xPrintf("MemoryAlloc\n");
    }
    ~MemoryAlloc()
    {
        if (_pBuf)
            free(_pBuf);
    }
    void* allocMemory(size_t nSize)
    {
        std::lock_guard<std::mutex> lg(_mutex);
        //如果内存池一开始没有被分配地址
        if (!_pBuf)
        {
            initMemory();
        }
        MemoryBlock* pReturn = nullptr;
        //当该内存池的空间不足(已经到了尾指针的地方),需要申请额外内存
        if (_pHeader == nullptr)
        {
            //给分配内存大小时,还要加上被分配内存的 描述信息的内存大小
            pReturn = (MemoryBlock*)malloc(_nSize+sizeof(MemoryBlock));
            pReturn->nID = -1;
            pReturn->nRef = 1;
            pReturn->pbool = false;
            pReturn->pAlloc = nullptr;
            pReturn->pNext = nullptr;
        }
        else
        {
            pReturn = _pHeader;
            _pHeader = _pHeader->pNext;
            assert(pReturn->nRef == 0);
            pReturn->nRef = 1;
        }
        //在Debug模式下输出调试信息  申请内存地址+内存块编号+内存块大小
        xPrintf("allocMem: %llx, id=%d, size=%d\n", pReturn, pReturn->nID, nSize);
        //申请内存之后返回给用户的因该是申请的内存,所有首地址要往后偏移一个内存块大小
        return ((char*)pReturn+sizeof(MemoryBlock));
    }
    //释放内存
    void freeMemory(void* pMem)
    {
        //首地址往前偏移,内存块和内存一起释放
        MemoryBlock* pBlock = (MemoryBlock*)((char*)pMem - sizeof(MemoryBlock));
        assert(1 == pBlock->nRef);

        //在内存池的部分释放之后,指针回到内存池头部
        if (pBlock->pbool)
        {
            std::lock_guard<std::mutex> lg(_mutex);
            //只被引用次数减1,只有被引用次数为0的时候才需要释放内存
            if (--pBlock->nRef != 0)
            {
                return;
            }
            //_pHeader是第一块空闲的内存,当pBlock释放出来之后,pBlock变成了第一块可用的内存
            //_pHeader变成了第二块
            //这里的额释放不是真正的把内存释放回给系统
            pBlock->pNext = _pHeader;
            _pHeader = pBlock;
        }
        //不在内存池的部分直接释放
        else 
        {
            if (--pBlock->nRef != 0)
            {
                return;
            }
            free(pBlock);
        }
    }
    //初始化内存池
    void initMemory()
    {
        xPrintf("initMemory:_nSize=%d,_nBlock=%d\n", _nSize, _nBlock);
        //断言
        assert(_pBuf == nullptr);
        //内存池为空,不初始化直接返回
        if (_pBuf)
            return;
        //计算内存池大小=内存块数量*(内存块大小+内存描述信息内存大小)
        size_t realSize = _nSize + sizeof(MemoryBlock);
        size_t bufSize = realSize * _nBlock;
        //向系统申请内存池大小
        _pBuf = (char*)malloc(bufSize);

        //初始化内存块头指针
        _pHeader = (MemoryBlock*)_pBuf;
        _pHeader->nID = 0;
        _pHeader->nRef = 0;
        _pHeader->pAlloc = this;
        _pHeader->pbool = true;
        _pHeader->pNext = nullptr;
        //遍历内存块并初始化
        MemoryBlock* pTemp1 = _pHeader;
        for (size_t i = 1; i < _nBlock; i++)
        {
            MemoryBlock* pTemp2 = (MemoryBlock*)(_pBuf + i * realSize);
            pTemp2->nID = i;
            pTemp2->nRef = 0;
            pTemp2->pAlloc = this;
            pTemp2->pbool = true;
            pTemp2->pNext = nullptr;
            pTemp1->pNext = pTemp2;
            pTemp1 = pTemp2;
        }


    }
protected:
    //内存池首地址
    char* _pBuf;
    //内存块的头指针
    MemoryBlock* _pHeader;
    //内存块的大小
    size_t _nSize;
    //内存块的数量
    size_t _nBlock;
    std::mutex _mutex;
    
};
//提供模板,便于在声明类成员变量是初始化MemoryAlloc的成员数据
template<size_t nSize,size_t nBlock>
class MemoryAlloctor:public MemoryAlloc
{
public:
    MemoryAlloctor()
    {
        //n指针大小
        const size_t n = sizeof(void*);
        //保证内存对齐
        if (nSize%n == 0)
            _nSize = nSize;
        else
            _nSize = (nSize / n)*n + n;
        _nBlock = nBlock;
    }
};
//内存池管理工具
class MemoryMgr
{
private:
    MemoryMgr()
    {
        //初始化内存池映射数组_szAlloc,当申请一个内存时,可以直接找到内存大小合适的内存池
        //init_szAlloc的功能类似映射,
        //如果申请内存的大小在[0,64]范围内,就在块大小为64的内存池去申请
        //如果申请内存的大小在[65,128]范围内,就在块大小为128的内存池去申请
        //.....
        init_szAlloc(0, 64, &_mem64);
        init_szAlloc(65, 128, &_mem128);
        init_szAlloc(129, 256, &_mem256);
        init_szAlloc(257, 512, &_mem512);
        init_szAlloc(513, 1024, &_mem1024);
    }
    ~MemoryMgr()
    {

    }
public:
    //单例模式+静态对象
    static MemoryMgr& Instance()
    {
        static MemoryMgr mgr;
        return mgr;
    }
    //申请内存
    void* allocMem(size_t nSize)
    {
        if (nSize <= MAX_MEMORY_SZIE)
        {
            return _szAlloc[nSize]->allocMemory(nSize);
        }
        else
        {
            //没有足够大小的内存池,去额外申请内存
            MemoryBlock* pReturn = (MemoryBlock*)malloc(nSize + sizeof(MemoryBlock));
            pReturn->pbool = false;
            pReturn->nID = -1;
            pReturn->nRef = 1;
            pReturn->pAlloc = nullptr;
            pReturn->pNext = nullptr;
            //在Debug模式下输出调试信息  申请内存地址+内存块编号+内存块大小
            xPrintf("allocMem: %llx, id=%d, size=%d\n", pReturn, pReturn->nID, nSize);
            //申请内存之后返回给用户的因该是申请的内存,所有首地址要往后偏移一个内存块大小
            return ((char*)pReturn + sizeof(MemoryBlock));
        }
    }
    //释放内存
    void freeMem(void* pMem)
    {
        //地址往前偏移,//内存块和内存一起释放
        MemoryBlock* pBlock = (MemoryBlock*)((char*)pMem - sizeof(MemoryBlock));
        //在Debug模式下输出调试信息  释放内存地址+内存块编号
        xPrintf("freeMem: %llx, id=%d\n", pBlock, pBlock->nID);
        //释放内存池
        if (pBlock->pbool)
        {
            pBlock->pAlloc->freeMemory(pMem);
        }
        //释放额外内存
        else
        {
            //只被引用一次就直接释放内存,若被多个引用暂时不释放(因为还被其它地方引用)
            if (--pBlock->nRef == 0)
                free(pBlock);
        }
    }
    //增加内存块的引用计数
    void addRef(void* pMem)
    {
        MemoryBlock* pBlock = (MemoryBlock*)((char*)pMem - sizeof(MemoryBlock));
        ++pBlock->nRef;
    }
private:
    //初始化内存池映射数组
    void init_szAlloc(int nBegin, int nEnd, MemoryAlloc* pMemA)
    {
        for (int i = nBegin; i <= nEnd; i++)
        {
            //内存池指针数组
            _szAlloc[i] = pMemA;
        }
    }
private:
    //内存池对外接口,不同大小的内存池
    MemoryAlloctor<64, 100000> _mem64;
    MemoryAlloctor<128, 100000> _mem128;
    MemoryAlloctor<256, 100000> _mem256;
    MemoryAlloctor<512, 100000> _mem512;
    MemoryAlloctor<1024, 100000> _mem1024;
    //内存池指针数组
    MemoryAlloc* _szAlloc[MAX_MEMORY_SZIE + 1];
};
#endif // !_MemoryMgr_hpp_

 

 

 4、main.cpp文件

#include"Alloctor.h"
#include<stdlib.h>
#include<iostream>
#include<thread>
#include<mutex>//
#include"CELLTimestamp.hpp"
using namespace std;
//原子操作   原子 分子 
mutex m;
//线程数
const int tCount = 8;
//内存申请次数
const int mCount = 100000;
//每个线程申请次数
const int nCount = mCount / tCount;
void workFun(int index)
{
    char* data[nCount];
    for (size_t i = 0; i < nCount; i++)
    {
        data[i] = new char[(rand() % 128) + 1];
    }
    for (size_t i = 0; i < nCount; i++)
    {
        delete[] data[i];
    }
    /*
    for (int n = 0; n < nCount; n++)
    {
        //自解锁
        //lock_guard<mutex> lg(m);
        //临界区域-开始
        //m.lock();

        //m.unlock();
        //临界区域-结束
    }//线程安全 线程不安全
     //原子操作 计算机处理命令时最小的操作单位
     */
}//抢占式

int main()
{
    thread t[tCount];
    for (int n = 0; n < tCount; n++)
    {
        t[n] = thread(workFun, n);
    }
    CELLTimestamp tTime;
    for (int n = 0; n < tCount; n++)
    {
        t[n].join();
        //t[n].detach();
    }
    cout << tTime.getElapsedTimeInMilliSec() << endl;
    cout << "Hello,main thread." << endl;
    system("pause");
    return 0;
}

5、计时器文件

 

#ifndef _CELLTimestamp_hpp_
#define _CELLTimestamp_hpp_

//#include <windows.h>
#include<chrono>
using namespace std::chrono;

class CELLTimestamp
{
public:
    CELLTimestamp()
    {
        update();
    }
    ~CELLTimestamp()
    {
    }

    void update()
    {
        //设置一个持续的时间段
        _begin = high_resolution_clock::now();
    }
   
    double getElapsedSecond()
    {
        //毫秒转化成秒
        return  getElapsedTimeInMicroSec() * 0.000001;
    }
  
    double getElapsedTimeInMilliSec()
    {
        //微秒转化成毫秒
        return this->getElapsedTimeInMicroSec() * 0.001;
    }
    /**
    *   获取微妙
    */
    long long getElapsedTimeInMicroSec()
    {
        //当前时间-进入时间,获取从开始计时到现在经历过的时间,以微妙为单位
        return duration_cast<microseconds>(high_resolution_clock::now() - _begin).count();
    }
protected:
    

    //设置一个高精度时间点
    time_point<high_resolution_clock> _begin;
};

#endif // !_CELLTimestamp_hpp_
#include"CELLTimestamp.hpp"

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!