基于一个简单定长内存池的实现方法详解_C 语言

    主要分为 3 个部分,memoryPool 是管理内存池类,block 表示内存块,chunk 表示每个存储小块。它们之间的关系为,memoryPool 中有一个指针指向某一起始 block,block 之前通过 next 指针构成链表结构的连接,每个 block 包含指定数量的 chunk。每次分配内存的时候,分配 chunk 中的数据地址。

主要数据结构设计:

Block:

复制代码 代码如下:

struct block {
    block * next;//指向下一个block指针
    unsigned int numofChunks;
    unsigned int numofFreeChunks;//剩余free的chunk数量
    unsigned int blockNum;//该block的编号
    char * data;
    queue<int> freepos; //记录可用chunk序号
};

MemoryPool:

复制代码 代码如下:

class memoryPool {
    unsigned int initNumofChunks; //每个block的chunk数量
    unsigned int chunkSize;//每个chunk的数据大小
    unsigned int steps;//每次扩展的chunk数量
    unsigned int numofBlocks;//当前管理多少个blocks
    block * blocksPtr;//指向起始block
    block * blocksPtrTail;//指向末尾block
    block * firstHasFreeChunksBlock;//指向第一个不为空的block
};

Chunk:

ChunkNum:该 chunk 所在 block 里的编号

blockAddress: 该 chunk 所对应的 block,主要用于 free 这个 chunk 的时候,能够快速找到所属 block,并进行相应更新

data:实际供使用的数据起始位置

关键操作说明:

内存分配:

从 firstHasFreeChunksBlock 开始查找第一个有 free 位置的 block,如果找到,则则获取该 block 的 freepos 的队首元素,返回该元素序号对应的 chunk 的数据地址,并将 freepos 的队首元素弹出,其他相关属性更新。如果找不到,则新增 steps 个 chunk,再重复上面的过程。

内存释放:

传入待释放的地址指针p,通过对p的地址移动可以找到chunk中的 ChunkNum 和 blockAddress 两个数据,通过 blockAddress 可以找到该 chunk 所属的 block,然后将ChunkNum 添加到该 block 的 freepos 中,其他相应属性更新。

使用方法:

复制代码 代码如下:

memoryPool * mp = new memoryPool (256);  
  char * s = (char *)mp->allocate();
  // 一些操作
  mp->freeMemory(s);    
  delete mp;

不足:

没考虑线程安全问题,该实现方案在单线程下可以正常运行。

程序源代码:

复制代码 代码如下:

#include <iostream>
#include <queue>
#include <string.h>
#include <ctime>
using namespace std;

struct block {
    block * next;
    unsigned int numofChunks;//指向下一个block指针
    unsigned int numofFreeChunks;//剩余free的chunk数量
    unsigned int blockNum;//该block的编号
    char * data;
    //记录可用chunk序号
    queue<int> freepos;
    block(unsigned int _numofChunks ,unsigned int _chunkSize, unsigned int _blockNum){
        numofChunks =  _numofChunks;
        numofFreeChunks = _numofChunks;
        blockNum = _blockNum;
        next = NULL;
        data = new char [numofChunks * (sizeof(unsigned int) + sizeof(void *) + _chunkSize)];
        char * p = data;
        //每个chunk的结构:4byte的chunk序号 + 4byte的所属block地址 + 真正的数据
        for(int i=0;i<numofChunks;i++){
            char * ptr = p + i * (_chunkSize +  sizeof(unsigned int) + sizeof(void *));
            unsigned int * num = (unsigned int *)(ptr);
            *num = i;
            ptr += sizeof(void *);
            int * blockpos = (int *) ptr;
            *blockpos = (int)this;
            freepos.push(i);
        }
    }
    ~block(){
        delete [] data;
    }
};

class memoryPool {
public :
    memoryPool(unsigned int _chunkSize = 256, unsigned int _initNumofChunks = 4096, unsigned int _steps = 64){
        initNumofChunks = _initNumofChunks;
        chunkSize = _chunkSize;
        steps = _steps;
        numofBlocks = steps;
        //创建内存池时,初始化一定数量的内存空间
        block * p = new block(initNumofChunks, chunkSize, 0);
        blocksPtr = p;
        for(int i=1;i<steps;i++){
            p->next = new block(initNumofChunks, chunkSize, i);
            p = p->next;
            blocksPtrTail = p;
        }
        firstHasFreeChunksBlock = blocksPtr;
    }
    ~memoryPool(){
        block  * p = blocksPtr;
        while(blocksPtr!=NULL){
            p = blocksPtr->next;
            delete blocksPtr;
            blocksPtr = p;
        }
    }

    /*
    从firstHasFreeChunksBlock开始查找第一个有free位置的block,
    如果找到,则则获取该block的freepos的对首元素,
    返回该元素序号对应的chunk的数据地址,并将freepos的队首元素弹出,
    其他相关属性更新。如果找不到,则新增steps个chunk,再重复上面的过程。
    */
    void * allocate(){
        block * p = firstHasFreeChunksBlock;
        while(p != NULL && p->numofFreeChunks <= 0) p = p->next;
        if(p == NULL){
            p = blocksPtrTail;
            increaseBlocks();
            p = p->next;
            firstHasFreeChunksBlock = p;
        }
        unsigned int pos =  p->freepos.front();
        void * chunkStart = (void *)(p->data + pos * (chunkSize +  sizeof(unsigned int) + sizeof(void *)));
        void * res = chunkStart + sizeof(unsigned int) + sizeof(void *);
        p->freepos.pop();
        p->numofFreeChunks --;
        return res;
    }

    void increaseBlocks(){
        block * p = blocksPtrTail;
        for(int i=0; i<steps; i++){
            p->next = new block(initNumofChunks, chunkSize, numofBlocks);
            numofBlocks++;
            p = p->next;
            blocksPtrTail = p;
        }
    }
    /*
    传入待释放的地址指针_data,
    通过对_data的地址移动可以找到chunk中的ChunkNum和blockAddress两个数据,
    通过blockAddress可以找到该chunk所属的block,
    然后将ChunkNum添加到该block的freepos中,其他相应属性更新。
    */
    void freeMemory(void * _data){
        void * p = _data;
        p -= sizeof(void *);
        int * blockpos = (int *) p;
        block * b = (block *) (*blockpos);
        p -= sizeof(unsigned int);
        int * num = (int *) p;
        b->freepos.push(*num);
        b->numofFreeChunks ++;
        if (b->numofFreeChunks > 0 && b->blockNum < firstHasFreeChunksBlock->blockNum)
            firstHasFreeChunksBlock = b;
    }

private :
    unsigned int initNumofChunks; //每个block的chunk数量
    unsigned int chunkSize;//每个chunk的数据大小
    unsigned int steps;//每次扩展的chunk数量
    unsigned int numofBlocks;//当前管理多少个blocks
    block * blocksPtr;//指向起始block
    block * blocksPtrTail;//指向末尾block
    block * firstHasFreeChunksBlock;//指向第一个不为空的block
};

//test
void echoPositionNum(char * p){
    p -= (sizeof(void *) + sizeof(unsigned int));
    int * num = (int *) p;
    cout<<*num<<endl;
}

//测试
void test0(){
    memoryPool mp;
    char * s1 = (char *)mp.allocate();
    char * s2 = (char *)mp.allocate();

    char str [256];
    char str2 [256];
    char str3 [256];
    for(int i=0; i<255; i++) {
        str[i] = 'a';str2[i] = 'b';str3[i] = 'c';
    }
    str[255] = '\0';
    str2[255] = '\0';
    strcpy(s1,str);
    strcpy(s2,str2);
    str3[255] = '\0';
    echoPositionNum(s1);

    cout<<s1<<endl;
    mp.freeMemory(s1);
    echoPositionNum(s2);
    cout<<s2<<endl;
    char * s3 = (char *)mp.allocate();
    strcpy(s3,str3);

    echoPositionNum(s3);
    cout<<s3<<endl;

}

void test1(){
    clock_t clock_begin = clock();
    const int N = 50000;
    char * s[N];
    int round = 100;
    while(round>=0){
        round --;
        for(int i=0;i<N;i++){
            s[i] = new char[256];
        }
        for(int i=0;i<N;i++){
             delete [] s[i];
        }
    }
    clock_t clock_end = clock();
    cout<<"Time cost\t"<<clock_end - clock_begin<<endl;
}

void test2(){

    memoryPool mp(256);
    clock_t clock_begin = clock();
    const int N = 50000;
    char * s[N];
    int round = 100;
    while(round>=0){
        round --;
        for(int i=0;i<N;i++){
            s[i] = (char *)mp.allocate();
        }
        for(int i=0;i<N;i++){
            mp.freeMemory(s[i]);
        }
    }
    clock_t clock_end = clock();
    cout<<"Time cost\t"<<clock_end - clock_begin<<endl;

}
int main()
{
    test0();
    test1();
    test2();
    return 0;
}

运行结果:


时间: 2024-09-30 21:46:05

基于一个简单定长内存池的实现方法详解_C 语言的相关文章

基于C++ list中erase与remove函数的使用详解_C 语言

erase的作用是,使作为参数的迭代器失效,并返回指向该迭代器下一参数的迭代器.如下: 复制代码 代码如下: list ParticleSystem;list::iterator pointer;if(pointer->dead == true){   pointer = ParticleSystem.erase(pointer);} 有一段关于错误使用erase的程序 复制代码 代码如下: using namespace std;int main(){  std::listtest_list;

基于Sizeof与Strlen的区别以及联系的使用详解_C 语言

一.sizeof    sizeof(...)是运算符,在头文件中typedef为unsigned int,其值在编译时即计算好了,参数可以是数组.指针.类型.对象.函数等.    它的功能是:获得保证能容纳实现所建立的最大对象的字节大小.    由于在编译时计算,因此sizeof不能用来返回动态分配的内存空间的大小.实际上,用sizeof来返回类型以及静态分配的对象.结构或数组所占的空间,返回值跟对象.结构.数组所存储的内容没有关系.    具体而言,当参数分别如下时,sizeof返回的值表示

C++内存泄漏及检测工具详解_C 语言

首先我们需要知道程序有没有内存泄露,然后定位到底是哪行代码出现内存泄露了,这样才能将其修复. 最简单的方法当然是借助于专业的检测工具,比较有名如BoundsCheck,功能非常强大,相信做C++开发的人都离不开它.此外就是不使用任何工具,而是自己来实现对内存泄露的监控,分如下两种情况: 一. 在 MFC 中检测内存泄漏 假如是用MFC的程序的话,很简单.默认的就有内存泄露检测的功能. 我们用VS2005生成了一个MFC的对话框的程序,发现他可以自动的检测内存泄露.不用我们做任何特殊的操作. 仔细

基于atoi()与itoa()函数的内部实现方法详解_C 语言

C语言提供了几个标准库函数,可以将任意类型(整型.长整型.浮点型等)的数字转换为字符串.以下是用itoa()函数将整数转 换为字符串的一个例子:       atoi     把字符串转换成整型数       itoa     把一整数转换为字符串 复制代码 代码如下:  #include "stdio.h"#include "ctype.h"#include "stdlib.h"/*Converts a character string int

深入C/C++浮点数在内存中的存储方式详解_C 语言

任何数据在内存中都是以二进制的形式存储的,例如一个short型数据1156,其二进制表示形式为00000100 10000100.则在Intel CPU架构的系统中,存放方式为  10000100(低地址单元) 00000100(高地址单元),因为Intel CPU的架构是小端模式.但是对于浮点数在内存是如何存储的?目前所有的C/C++编译器都是采用IEEE所制定的标准浮点格式,即二进制科学表示法.在二进制科学表示法中,S=M*2^N 主要由三部分构成:符号位+阶码(N)+尾数(M).对于flo

C++中用两个标准容器stack,实现一个队列的方法详解_C 语言

代码如下所示: 复制代码 代码如下: // StackToQueue.cpp : 定义控制台应用程序的入口点.//用两个标准容器stack,实现一个队列#include "stdafx.h"#include <iostream>#include <stack>using namespace std;template <class T>class StackToQueue{public: StackToQueue() {  stack1;  stack

利用ace的ACE_Task等类实现线程池的方法详解_C 语言

本代码应该是ace自带的例子了,但是我觉得是非常好的,于是给大家分享一下.注释非常详细啊.头文件 复制代码 代码如下: #ifndef THREAD_POOL_H#define THREAD_POOL_H/* In order to implement a thread pool, we have to have an object that   can create a thread.  The ACE_Task<> is the basis for doing just   such a

定长内存池之BOOST::pool

内存池可有效降低动态申请内存的次数,减少与内核态的交互,提升系统性能,减少内存碎片,增加内存空间使用率,避免内存泄漏的可能性,这么多的优点,没有理由不在系统中使用该技术. 内存池分类: 1.              不定长内存池.典型的实现有apr_pool.obstack.优点是不需要为不同的数据类型创建不同的内存池,缺点是造成分配出的内存不能回收到池中.这是由于这种方案以session为粒度,以业务处理的层次性为设计基础. 2.             定长内存池.典型的实现有LOKI.B

线程池的原理与实现详解_C 语言

一. 线程池的简介通常我们使用多线程的方式是,需要时创建一个新的线程,在这个线程里执行特定的任务,然后在任务完成后退出.这在一般的应用里已经能够满足我们应用的需求,毕竟我们并不是什么时候都需要创建大量的线程,并在它们执行一个简单的任务后销毁. 但是在一些web.email.database等应用里,比如彩铃,我们的应用在任何时候都要准备应对数目巨大的连接请求,同时,这些请求所要完成的任务却又可能非常的简单,即只占用很少的处理时间.这时,我们的应用有可能处于不停的创建线程并销毁线程的状态.虽说比起