内存池总结

建议先看看这篇
内存池比malloc的好处

经典的内存池实现

参考:
1.C++ 应用程序性能优化,第 6 章:内存池
https://www.ibm.com/developerworks/cn/linux/l-cn-ppp/index6.html
2. C++ 内存池介绍与经典内存池的实现
https://blog.csdn.net/K346K346/article/details/49538975

下面的经典内存池实现来自参考2

  1. 经典内存池实现过程
    (1)先申请一块连续的内存空间,该段内存空间能够容纳一定数量的对象;
    (2)每个对象连同一个指向下一个对象的指针一起构成一个内存节点(Memory Node)。各个空闲的内存节点通过指针形成一个链表,链表的每一个内存节点都是一块可供分配的内存空间;
    (3)某个内存节点一旦分配出去,从空闲内存节点链表中去除;
    (4)一旦释放了某个内存节点的空间,又将该节点重新加入空闲内存节点链表;
    (5)如果一个内存块的所有内存节点分配完毕,若程序继续申请新的对象空间,则会再次申请一个内存块来容纳新的对象。新申请的内存块会加入内存块链表中。

经典内存池的实现过程大致如上面所述,其形象化的过程如下图所示:
在这里插入图片描述
如上图所示,申请的内存块存放三个可供分配的空闲节点。空闲节点由空闲节点链表管理,如果分配出去,将其从空闲节点链表删除,如果释放,将其重新插入到链表的头部。如果内存块中的空闲节点不够用,则重新申请内存块,申请的内存块由内存块链表来管理。

注意,本文涉及到的内存块链表和空闲内存节点链表的插入,为了省去遍历链表查找尾节点,便于操作,新节点的插入均是插入到链表的头部,而非尾部。

  1. 经典内存池数据结构设计

按照上面的过程设计,内存池类模板有这样几个成员。

两个指针变量:

内存块链表头指针:pMemBlockHeader;
空闲节点链表头指针:pFreeNodeHeader;

空闲节点结构体:

struct FreeNode
{
	FreeNode* pNext;
	char data[ObjectSize];
};

内存块结构体:

struct MemBlock
{
	MemBlock *pNext;
	FreeNode data[NumofObjects];
};

  1. 经典内存池的实现
    根据以上经典内存池的设计,编码实现如下。
#include <iostream>
using namespace std;

template<int ObjectSize, int NumofObjects = 20>
class MemPool
{
private:
	//空闲节点结构体
	struct FreeNode
	{
		FreeNode* pNext;
		char data[ObjectSize];
	};

	//内存块结构体
	struct MemBlock
	{
		MemBlock* pNext;
		FreeNode data[NumofObjects];
	};

	FreeNode* freeNodeHeader;
	MemBlock* memBlockHeader;

public:
	MemPool()
	{
		freeNodeHeader = NULL;
		memBlockHeader = NULL;
	}

	~MemPool()
	{
		MemBlock* ptr;
		while (memBlockHeader)
		{
			ptr = memBlockHeader->pNext;
			delete memBlockHeader;
			memBlockHeader = ptr;
		}
	}
	void* malloc();
	void free(void*);
};

//分配空闲的节点
template<int ObjectSize, int NumofObjects>
void* MemPool<ObjectSize, NumofObjects>::malloc()
{
	//无空闲节点,申请新内存块
	if (freeNodeHeader == NULL)
	{
		MemBlock* newBlock = new MemBlock;
		newBlock->pNext = NULL;

		freeNodeHeader=&newBlock->data[0];	 //设置内存块的第一个节点为空闲节点链表的首节点
		//将内存块的其它节点串起来
		for (int i = 1; i < NumofObjects; ++i)
		{
			newBlock->data[i - 1].pNext = &newBlock->data[i];
		}
		newBlock->data[NumofObjects - 1].pNext=NULL;

		//首次申请内存块
		if (memBlockHeader == NULL)
		{
			memBlockHeader = newBlock;
		}
		else
		{
			//将新内存块加入到内存块链表
			newBlock->pNext = memBlockHeader;
			memBlockHeader = newBlock;
		}
	}
	//返回空节点闲链表的第一个节点
	void* freeNode = freeNodeHeader;
	freeNodeHeader = freeNodeHeader->pNext;
	return freeNode;
}

//释放已经分配的节点
template<int ObjectSize, int NumofObjects>
void MemPool<ObjectSize, NumofObjects>::free(void* p)
{
	FreeNode* pNode = (FreeNode*)p;
	pNode->pNext = freeNodeHeader;	//将释放的节点插入空闲节点头部
	freeNodeHeader = pNode;
}

class ActualClass
{
	static int count;
	int No;

public:
	ActualClass()
	{
		No = count;
		count++;
	}

	void print()
	{
		cout << this << ": ";
		cout << "the " << No << "th object" << endl;
	}

	void* operator new(size_t size);
	void operator delete(void* p);
};

//定义内存池对象
MemPool<sizeof(ActualClass), 2> mp;

void* ActualClass::operator new(size_t size)
{
	return mp.malloc();
}

void ActualClass::operator delete(void* p)
{
	mp.free(p);
}

int ActualClass::count = 0;

int main()
{
	ActualClass* p1 = new ActualClass;
	p1->print();

	ActualClass* p2 = new ActualClass;
	p2->print();
	delete p1;

	p1 = new ActualClass;
	p1->print();

	ActualClass* p3 = new ActualClass;
	p3->print();

	delete p1;
	delete p2;
	delete p3;
}

程序运行结果:

004AA214: the 0th object
004AA21C: the 1th object
004AA214: the 2th object
004AB1A4: the 3th object
  1. 程序分析

可以看出上面的方案类似于boost的内存池实现,一个内存池只管理一种类型的数据,即对于每一个类,都需要实例化对应的一个内存池,然后为类重载operator new和operator delete。

阅读以上程序,应注意以下几点。
(1)对一种特定的类对象而言,内存池中内存块的大小是固定的,内存节点的大小也是固定的。内存块在申请之初就被划分为多个内存节点,每个 Node 的大小为 ItemSize。刚开始,所有的内存节点都是空闲的,被串成链表。

(2)成员指针变量 memBlockHeader 是用来把所有申请的内存块连接成一个内存块链表,以便通过它可以释放所有申请的内存。freeNodeHeader 变量则是把所有空闲内存节点串成一个链表。freeNodeHeader为空则表明没有可用的空闲内存节点,必须申请新的内存块。

(3)申请空间的过程如下。在空闲内存节点链表非空的情况下,malloc 过程只是从链表中取下空闲内存节点链表的头一个节点,然后把链表头指针移动到下一个节点上去。否则,意味着需要一个新的内存块。这个过程需要申请新的内存块切割成多个内存节点,并把它们串起来,内存池技术的主要开销就在这里。

(4)释放对象的过程就是把被释放的内存节点重新插入到内存节点链表的开头。最后被释放的节点就是下一个即将被分配的节点。

(5)内存池技术申请/释放内存的速度很快,其内存分配过程多数情况下复杂度为 O(1),主要开销在 freeNodeHeader 为空时需要生成新的内存块。内存节点释放过程复杂度为 O(1)。

(6) 在上面的程序中,指针 p1 和 p2 连续两次申请空间,它们代表的地址之间的差值为 8,正好为一个内存节点的大小(sizeof(FreeNode))。指针 p1 所指向的对象被释放后,再次申请空间,得到的地址与刚刚释放的地址正好相同。指针 p3 多代表的地址与前两个对象的地址相聚很远,原因是第一个内存块中的空闲内存节点已经分配完了,p3 指向的对象位于第二个内存块中。

以上内存池方案并不完美,比如,只能单个单个申请对象空间,不能申请对象数组,内存池中内存块的个数只能增大不能减少,未考虑多线程安全等问题。

标准库的std::allocator

头文件

#include <memory>

类模板声明

参考:https://docs.microsoft.com/zh-cn/cpp/standard-library/allocator-class?view=vs-2019

template< class T >
struct allocator;

成员

类型
const_pointer指向由分配器管理的对象类型的常量指针的类型
const_reference对由分配器管理的对象类型的常量引用的类型
difference_type有符号的整型,可以表示指向由分配器管理的对象类型的指针值之间的差异
pointer提供指向由分配器管理的对象类型的指针的类型
reference提供指向对分配器管理的对象类型的引用的类型
size_type一种无符号整数类型,该类型可以表示 allocator 类型的对象可以分配的任何序列的长度。
value_type由分配器管理的类型。
成员函数
allocator用于创建 allocator 对象的构造函数
address查找指定了其值的对象的地址.
allocate分配大小足以存储至少某个指定数量的元素的内存块
construct在使用指定值初始化的指定地址处构造特定类型的对象
deallocate从指定位置开始从存储中释放指定数量的的对象
destroy调用对象析构函数而不释放存储对象的内存
max_size返回在可用内存用完之前,可以由类 allocator 的对象分配的类型 Type 的元素数
rebind使得一种类型的对象分配器可以为另一种类型的对象分配存储的结构
operator=将一个 allocator 对象分配给另一个 allocator 对象

实现

参考:https://blog.csdn.net/xiyanggudao/article/details/51543839

  • 一个简单的实现如下:
    简单的对operator new和operator delete进行封装的实现
template<class T>
class allocator
{
public:
    // 1、为什么需要下面这些成员,有什么作用呢?
    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;       // size_t是无符号整数
    // ptrdiff_t是有符号整数,代表指针相减结果的类型
    typedef ptrdiff_t  difference_type;
 
    // 2、这是做什么用的,为何template是U,而不是与allocator的T一致?
    template<class U>
    struct rebind
    {
        typedef allocator<U> other;
    };
 
    // 默认构造函数,什么都不做
    allocator() noexcept
    {
    }
 
    // 泛化的构造函数,也什么都不做
    // 3、为什么需要这个泛化的构造函数,不同类型的allocator复制合适吗?
    template<class U>
    allocator(const allocator<U>&) noexcept
    {
    }
 
    // 析构函数,什么都不做
    ~allocator() noexcept
    {
    }
 
    // 返回对象地址
    pointer address(reference val) const noexcept
    {
        //non-const版调用const版,参见《Effective C++》条款3
        return const_cast<reference>(address(static_cast<const_reference>(val)));
    }
 
    // 返回对象地址
    const_pointer address(const_reference val) const noexcept
    {
        return &val;
    }
 
    // 申请内存,count是指对象个数,不是字节数。
    // 4、hint是做什么的?
    pointer allocate(size_type count, allocator<void>::const_pointer hint = nullptr)
    {
        return static_cast<pointer>(::operator new(count * sizeof(value_type)));
    }
 
    // 释放内存
    void deallocate(pointer ptr, size_type count)
    {
        ::operator delete(ptr);
    }
 
    // 可配置的最大量(指对象个数,不是字节数)
    size_type max_size() const noexcept
    {
        return (static_cast<size_type>(-1) / sizeof(value_type));
    }
 
    // 构造对象,Args是模板参数包,见《C++ Primer》第5版16.4节
    template <class U, class... Args>
    void construct(U* p, Args&&... args)
    {
        ::new ((void *)p) U(::std::forward<Args>(args)...);
    }
 
    // 析构对象
    template <class U>
    void destroy(U* p)
    {
        p->~U(); // 原来模板还可以这样用
    }
};
 
// 5、为什么要对void进行特化?
template<>
class allocator<void>
{
public:
    typedef void value_type;
    typedef void *pointer;
    typedef const void *const_pointer;
    template <class U> struct rebind
    {
        typedef allocator<U> other;
    };
};

问题解答
1、STL的规范,同时这些type在迭代器和traits技术中有用。
2、此结构可用于为与所实现的容器的元素类型不同的类型分配内存。
成员类模板定义其他类型。 其唯一目的是在给出类型名称 allocator< Type> 时提供类型名称 allocator<_ Other>
例如,给定分配器对象 al 类型 A,可以使用表达式来分配 _Other 类型的对象:
A::rebind::other(al).allocate(1, (Other *)0)
或者,可以通过编写类型来命名其指针类型:
A::rebind::other::pointer
3、allocator类的模板参数只有一个,代表分配的元素类型,如果allocator封装的仅是内存的分配策略而与元素类型无关,定义泛型复制构造好像没什么不合理,同时如果不定义成泛型rebind将无法使用。construct成员函数和destroy成员函数也是泛型,allocator的使用条件还是特别宽松的。
4、hint
Either 0 or a value previously obtained by another call to allocate and not yet freed with deallocate.
When it is not 0, this value may be used as a hint to improve performance by allocating the new block near the one specified. The address of an adjacent element is often a good choice.
5、只有void *变量,没有void变量,没有void&变量,不能typedef void value_type等等。

使用示例

#include <memory>
#include <iostream>
#include <string>
 
int main()
{
    std::allocator<int> a1;   // default allocator for ints
    int* a = a1.allocate(1);  // space for one int
    a1.construct(a, 7);       // construct the int
    std::cout << a[0] << '\n';
    a1.deallocate(a, 1);      // deallocate space for one int
 
    // default allocator for strings
    std::allocator<std::string> a2;
 
    // same, but obtained by rebinding from the type of a1
    decltype(a1)::rebind<std::string>::other a2_1;
 
    // same, but obtained by rebinding from the type of a1 via allocator_traits
    std::allocator_traits<decltype(a1)>::rebind_alloc<std::string> a2_2;
 
    std::string* s = a2.allocate(2); // space for 2 strings
 
    a2.construct(s, "foo");
    a2.construct(s + 1, "bar");
 
    std::cout << s[0] << ' ' << s[1] << '\n';
 
    a2.destroy(s);
    a2.destroy(s + 1);
    a2.deallocate(s, 2);
}

SGI STL中的内存池实现

内存池的原理简单来讲就是一次性的向系统申请大量的内存,之后再有内存请求的时候,如果内存池的内存大小能够满足请求,就从内存池里分配,不必再进行系统调用,从而实现性能提升,而多次的内存申请系统调用,很容易生成内存碎片而造成内存浪费

____________________________________________________参考:《STL源码剖析》

  • STL内存分配分为一级分配器和二级分配器,一级分配器就是采用malloc分配内存,二级分配器采用内存池。
  • 当所需的内存大小大于128bytes时直接调用malloc分配内存,否则从free list中分配,这样就避免太多小内存块造成的内存碎片和管理内存的额外负担造成内存利用率不高的问题。

SGI中的内存池: 使用两根指针 start_free 和 end_free 标记其范围的一段连续的堆空间,不负责对内存的分配与回收。
内存分配与回收的具体工作交给一个 数组+链表 的结构去处理,具体如下图:
在这里插入图片描述

  • 第一级配置器__malloc_alloc_template
//malloc-based allocator .通常比稍后介绍的default alloc速度慢
//一般而言是thread-sate,并且对于空间的运用比较高级
//以下是第一级配置器
//注意,无“template型别参数”,至于“非型别参数”inst,则完全没派上用场
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);
static void (*__malloc_alloc_oom_handler)();

public:
static void *allocate(size_t n){
    void *result=malloc(n);//第一级配置器直接使用malloc()
    //以下无法满足需求时,改用oom_malloc()
    if(0==result) result==oom_malloc(n);
    return result;
}
static void deallocate(void *p,size_t){
    free(p);//第一级配置器直接使用free()
}
static void* reallocate(void *p,size_t,size_t new_sz){
    void *result=realloc(p,new_sz);//第一级配置器直接使用realloc()
    //以下无法满足需求时,改用oom_realloc()
    if(0==result) result=oom_realloc(p,new_sz);
    return result;
}
//以下仿真C++的set_new_handler(),你可以通过它指定你自己的out-of-memory handler
static void(*set_malloc_handler(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;

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);
    }
}

template<int inst>
void * __malloc_alloc_template<inst>::oom_realloc(void *p,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=realloc(p,n);//再次尝试配置内存
        if(result) return(result);
    }
}
//注意,以下直接将参数inst指定为0
typedef __malloc_alloc_template<0> malloc_alloc;
  • 第二级配置器 __default_alloc_template
    SGI第二级配置器的做法是,如果区块够大,超过128bytes时,就移交第一级配置器处理,当区块小于128bytes,则以内存池管理:每次配置一大块内存,并维护对应之free list。若下次再有相同大小的内存需求,就直接从free-lists中拔出。如果客端释放小额区块,就由配置器回收到free-lists中

为了方便管理,SGI第二级配置器会主动将任何小额区块的内存需求量上调至8的倍数(例如客端要求30bytes,就自动调整为32bytes),并维护16个free list,各自管理大小分别为8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128bytes

  • free_lists节点结构如下:
union obj{
    union obj* free_list_link;
    char client_data[1];
}

union每次仅能使用一个成员,free_list_link指向下个内存块的地址,client_data指向内存块首地址。
关于为啥这里加上char client_data[1],比较认同如下一个回答
https://blog.csdn.net/w450468524/article/details/51649222

由于free_list_link长度较大,union obj的长度为它的整数倍,client_data[1]只占了其头部的一个字节。因此client_data首地址指向的也就是整个union obj的首地址,亦即实际区块的地址。
简单测试下:
#include
#include
union obj{
union obj * free_list_link;
char client_data[1]; /* The client sees this.*/
};
int main(){
obj * block = (obj ) malloc(128);
printf("%x\n", block);
printf("%x\n", block->client_data);
return 0;
}
结果为:
1c9c010
1c9c010
这里client_data其实是作为一种指针类型转换,为了方便使用。一般内存buffer我们都是使用char
指针,如果要使用obj的地址,还得加上 (char *)myBlock 进行强制类型转换。反之直接使用myBlock->client_data即可,方便多了。

下面是第二级配置器的部分实现内容:

enum{__ALIGN=8};//小型区块的上调边界
enum{__MAX_BYTES=128};//小型区块的上限
enum{__NFREELISTS=__MAX_BYTES/__ALIGN};//free lists个数

//以下是第二级配置器
//注意,无'template型别参数',且第二参数完全无用
//第一参数用于多线程环境下,
template<bool threads,int inst>
class __default_alloc_template{
private:
    //ROUND_UP()将bytes上调至8的倍数
    static size_t ROUND_UP(size_t bytes){
        return (((bytes)+__ALIGN-1)&~(__ALIGN-1));//先加7,然后低3位清零
    }
private:
    union obj{//free-lists的节点构造
        union obj* free_list_link;
        char client_data[1];
    };
private:
    //16个free lists
    static obj* volatile free_list[__NFREELISTS];
    //以下函数根据区块大小,决定使用第n号free list。n从1起算
    static size_t FREELIST_INDEX(size_t bytes){
        return (((bytes)+__ALIGN-1)/__ALIGN-1);
    }

    //返回一个大小为n的对象,并可能加入大小为n的其他区块到free list
   static void *refill(size_t n);
   //配置一大块空间,可容纳nobjs个大小为“size"的区块
   //如果配置nobjs个区块有所不便,nobjs可能会降低
   static char *chunk_alloc(size_t size,int &nobjs);
   //chunk allocation state
   static char *start_free;//内存池起始位置,只在chunk_alloc()中变化
   static char *end_free;//内存池结束位置,只在chunk_alloc()中变化
public:
    //
    static void *allocate(size_t n);
    static void *deallocate(void *p,size_t n);
    static void *reallocate(void *p,size_t old_sz,size_t new_sz);   
};
//static data member 的定义与初值设定
template <bool threads,int inst>
char *__default_alloc_template<threads,inst>::start_free=0;
template <bool threads,int inst>
char *__default_alloc_template<threads,inst>::end_free=0;
template <bool threads,int inst>
char *__default_alloc_template<threads,inst>::heap_size=0;

template <bool threads,int inst>
__default_alloc_template<threads,inst>:obj * volatile
__default_alloc_template<threads,inst>::free_list[_NFREELISTS]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
  • 空间配置函数allocate()
    先判断区块大小,大于128bytes就调用第一级空间配置器,小于128bytes则检查对应的free list.如果free list 之间有可用的区块,就直接拿来用,如果没有可用区块,就将区块大小上调至8的倍数边界,然后调用refill()。
static void * allocate(size_t n)
{
    obj * volatile * my_free_list;    //volatile关键字禁止程序向寄存器取值,二级指针指向指针数组。
    obj * result;
    //如果大于128byte,直接到堆区内存申请。
    if( n > (size_t)__MAX_BYTES)
    {
        return (malloc_alloc::allocate(n));
    }
    
    //否则,向自由链表申请,若自由链表不足,直接到内存池申请。
    //寻找16个free lists中适合的一个
    my_free_list = free_list + FREELIST_INDEX(n);
    result = *my_free_list;
    if(result == 0)
    {
       //没有找到可用的free list,准备重新填充free list
        void *r = refill(ROUND_UP(n));
        return r;
    }
    //调整free list
    *my_free_list = result->free_list_link;
    return (result);
}

在这里插入图片描述

  • 空间释放函数deallocate()
    先判断区块大小,大于128bytes就调用第一级配置器,小于128bytes就找出对应的free list,将区块回收
//p不可以是0,注意这里的n是啥?
//前面说过SGI第二级配置器会主动将任何小额区块的内存需求量上调至8的倍数(例如客端要求申请一个int类型的内存空间4Bytes,就自动调整为8bytes),所以释放区块的时候传入的n是客端需要的大小(sizeof(int):4Bytes),但是deallocate函数内部必须用FREELIST_INDEX(n)上调至8的倍数,因为它知道虽然客端释放的是4Bytes内存块,但自己当初给它的是8Bytes
static void deallocate(void *p,size_t n)
{
    obj *q=(obj*)p;
    obj *volatile *my_free_list;
    //大于128就调用第一级配置器
    if(n>(size_t) __MAX_BYTES){
        malloc_alloc::deallocate(p,n);
        return ;
    }
    //寻找对应的free list
    my_free_list =free_list+FREELIST_INDEX(n);
    //调整free list,回收区块
    q->free_list_link=*my_free_list;
    *my_free_list=q;
}

在这里插入图片描述

  • 重新填充free lists函数refill()
    在用allocate()配置空间时,当发现free list 中没有可用区块时,就调用refill(),准备为free list重新填充空间。新的空间将取自内存池(调用chunk_alloc())。缺省取得20个新节点,但万一内存池空间不足,获得的节点数(区块数)可能小于20:
//返回一个大小为n的对象,并且有时候会为适当的free list增加节点
//假设n已经适当上调至8的倍数
template<bool threads,int inst>
void* __default_alloc_template<threads,inst>::refill(size_t n)
{
    int nobjs=20;
    //调用chunk_alloc(),尝试取得nobjs个区块作为free list的新节点
    //注意参数nobjs是pass by reference
    char * chunk=chunk_alloc(n,nobjs);
    obj* volatile* my_free_list;
    obj* result;
    obj* current_obj,*next_obj;
    int i;
    
    //如果只获得一个区块,这个区块就分配给调用者用,free list无新节点
    if(1==nobjs) return(chunk);
    //否则准备调整free list,纳入新节点
    my_free_list=free_list+FREELIST_INDES(n);
    
    //以下在chunk空间内建立free list
    result=(obj*) chunk;//这一块准备返回给客端
    //以下导引free list指向新配置的空间(取自内存池)
    *my_free_list=next_obj=(obj*)(chunk+n);
    //以下将free list的各节点串接起来
    for(i=1;;i++){//从1开始,因为第0个将返回给客端
        current_obj=next_obj;
        next_obj=(obj*)((char*)next_obj+n);
        if(nobjs-1==i){
            current_obj->free_list_link=0;
            break;
        }else{
            current_obj->free_list_link=next_obj;
        }
    }
    return (result);
}

  • 内存池
    从内存池中取空间给free list 使用,是chunk_alloc()的工作:
    这里内存池只是使用两根指针 start_free 和 end_free 标记其范围的一段连续的堆空间而已,不负责对内存的分配与回收。内存分配与回收的具体工作交给一个 数组+链表 的结构去处理
//假设size已经适当上调至8的倍数
//注意参数nobjs是pass by reference
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{
        //内存池剩余空间连一个区块的大小都无法提供
        size_t bytes_to_get=2*total_bytes+ROUND_UP(heap_size>>4);
        //以下试着让内存池中的残余零头还有利用价值
        if(bytes_left>0){
        //内存池内还有一些零头,先配给适当的free list
        //首先寻找适当的free list,这里bytes_left已是8的倍数
        obj* volatile* my_free_list=free_list+FREELIST_INDEX(bytes_left);
        //调整free list,将内存池中残余空间编入
        ((obj*)start_free)->free_list_link=*my_free_list;
        *my_free_list=(obj*)start_free;
    }
    //配置heap空间,用来补充内存池
    start_free=(char*)malloc(bytes_to_get);
    if(0==start_free){
        //heap空间不足,malloc()失败
        int i;
        obj* volatile* my_free_list,*p;
        //试着检视我们手上拥有的东西,这不会造成伤害,我们不打算尝试配置
        //较小的区块,因为那在多进程机器上容易导致灾难
        //以下搜寻适当的free list
        //所谓适当是指“尚有未用区块,且区块够大”之free list
        for(i=size;i<=__MAX_BYTES;i+=__ALIGN){
          my_free_list=free_list+FREELIST_INDEX(i);
          p=*my_free_list;
          if(0!=p){
             //调整free list以释出未用区块
             *my_free_list=p->free_list_link;
             start_free=(char*)p;
             end_free=start_free+i;
             //递归调用自己,为了修正nobjs
             return (chunk_alloc(size,nobjs));
             //注意,任何残余零头终将被编入适当的free-list中备用
           }
        }
      end_free=0;//如果出现意外(到处都没有内存可用)
      //调用第一级配置器,看看out_of_memory机制能否尽点力
      start_free=(char*)malloc_alloc::allocate(bytes_to_get);
      //这会导致抛出异常(exception),或内存不足的情况获得改善
    }
    heap_size+=bytes_to_get;
    end_free=start_free+bytes_to_get;
    //递归调用自己,为了修正nobjs
    return (chunk_alloc(size,nobjs));
    }
}

在这里插入图片描述

  • 令alloc为第二级配置器
typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS,0> alloc;
  • SGI为alloc再包装一个simple_alloc接口如下,使得配置器的接口能够符合STL规格
//SGI为alloc再包装一个接口如下,使得配置器的接口能够符合STL规格
template<class T,class Alooc>
class simple_alloc{
public:
    static T *allocate(size_t n){
        return 0==n?0:(T*)Alloc::allocate(n*sizeof(T));
    }
    static T *allocate(void){
        return (T*)Alloc::allocate(sizeof(T));
    }
    static void deallocate(T *p,size_t n){
        if(0!=)Alloc::deallocate(p,n*sizeof(T));
    }
    static void deallocate(T *p){
        Alloc::deallocate(p,sizeof(T));
    }
};

SGI STL的容器全都使用这个simple_alloc接口,例如:

template <class T,class Alloc=alloc>//缺省使用alloc为配置器
class vector{
protected:
//专属的空间配置器,每次配置一个元素大小
typedef simple_alloc<value_type,Alloc> data_allocator;

void deallocate(){
    if(...)
        data_allocator::deallocate(start,end_of_storage-start);
}
...
}

github上的一个内存池

博文:
https://blog.csdn.net/hansionz/article/details/87885229
github:https://github.com/hansionz/ConcurrentMemoryPool

待续。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值