c++——空间配置器

以下是阿狸对c++中空间适配器的学习总结吗,希望可以帮助到大家。

什么是空间是适配器?

顾名思义就是为了高效的管理空间,进行空间的申请与回收。其在我们使用c++时候,一直在默默的工作。虽然我们在日常工作中用不到,但是了解它的原理,就像了解一件事情的背后,对我们理解c++有更好的帮助。

生成结果

接下来就让我为大家来揭秘!

为什么需要空间适配器呢?

我们在c++中使用vector,map等容器的时候,所需要的空间都是需要通过new来申请的这样代码虽然可以运行,但是会有以下不足之处。

1:空间的申请与释放需要用户自己管理,容易造成内存泄漏

2:频繁向系统申请小块内存快,容易造成内存碎片

3:频繁向系统申请小块内存,影响程序运行效率

4:直接使用malloc和new进行申请,会对空间进行浪费

5:空间申请失败怎么办

6:代码结构混乱,代码服用率低

7:存在线程安全问题

生成结果

因此,我们需要设计一块高效的内存管理机制!即SGL-STL空间适配器

总结上面我们遇到的问题,主要还是向系统频繁的申请小块内存所导致的;那么多大的内存算是小块内存呢?在SGL-STL中以128作为小块内存和大块内存的分界线,将空间配置器分为两级。

如下所示:

ok,接下来让我们一起看看一二级空间适配器的区别

首先让我们看看一级空间配置器

一级空间适配器的原理是非常简单的,其直接对malloc和free进行了封装。因为简单,所以让我们一起来欣赏这段优美的代码。

//一级空间适配器
template <int inst>
class __malloc_alloc_template
{

private:
	static void *oom_malloc(size_t);

public:
	// 对malloc的封装
	static void * allocate(size_t n) {

		// 申请空间成功,直接返回,失败交由oom_malloc处理
		void *result = malloc(n);

		if (0 == result)
			result = oom_malloc(n);

		return result;
	}

	// 对free的封装
	static void deallocate(void *p, size_t /* n */){
		free(p);
	}

	// 模拟set_new_handle
	// 该函数的参数为函数指针,返回值类型也为函数指针
	// void (* set_malloc_handler( void (*f)() ) )()
	static void(*set_malloc_handler(void(*f)()))(){
		void(*old)() = __malloc_alloc_oom_handler;
		__malloc_alloc_oom_handler = f;
		return(old);
	}
};
// malloc申请空间失败时代用该函数
template <int inst>
void * __malloc_alloc_template<inst>::oom_malloc(size_t n) {
	void(*my_malloc_handler)();
	void *result;
	for (;;)
	{
		// 检测用户是否设置空间不足应对措施,如果没有设置,抛异常,模式new的方式
		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);
	}
}
typedef __malloc_alloc_template<0> malloc_alloc;

总结老来说,一级空间适配器,就是对malloc和free的封装,只是在其上增加的set_new_handle(空间不足时的自定义操作)的思想。

接下来就让我们看看二级空间适配器

二级空间适配器专门负责小于128byte的小内存块。

那么其实怎样提升小块内存的申请与释放的呢?

首先SGI-STL采用了内存池的技术来提高申请空间的速度以及减少空间的浪费,采用哈希桶的方式来提高用户获得空间的速度&高效管理。

内存池:先申请一块较大的内存块做备用,当需要内存时,直接到内存池中去取,当内存池中空间不够时,再向内存中去取,当用户不用时,直接还给内存池即可。这样避免了向系统申请小块内存而造成效率低,内存碎片以及额外浪费的问题。

往往内存池会采用链表的进行管理。

但是SGI-STL中的二级空间配置器并没有采用链表的方式管理内存池,而是采用了哈希桶的方式进行了管理。

如下图所示:

根据上图我们可看出,二级空间适配器中的内存池以8byte为单位进行了对齐。

那么,为什么以8为单位对齐呢?这是因为要考虑指针,这样32位下的4字节指针和64位的8字节指针都可以在内存池中放下了。

ok,接下来就让我们看看二级空间适配器的流程。

看完流程,接下来让我们欣赏下这段优美的代码

前期准备的代码;

// 去掉代码中繁琐的部分
template <int inst>
class __default_alloc_template
{
private:
	enum { __ALIGN = 8 }; // 如果用户所需内存不是8的整数倍,向上对齐到8的整数倍
	enum { __MAX_BYTES = 128 }; // 大小内存块的分界线
	enum { __NFREELISTS = __MAX_BYTES / __ALIGN }; // 采用哈希桶保存小块内存时所需桶的个数

	// 如果用户所需内存块不是8的整数倍,向上对齐到8的整数倍
	static size_t ROUND_UP(size_t bytes)
	{
		return (((bytes)+__ALIGN - 1) & ~(__ALIGN - 1));
	}

private:
	// 用联合体来维护链表结构,这样可以使整体占用一块内存,而结构体可能会占用多个地方的内存
	union obj
	{
		union obj * free_list_link;
		char client_data[1]; /* The client sees this. */
	};

private:
	static obj * free_list[__NFREELISTS];

	// 哈希函数,根据用户提供字节数找到对应的桶号
	static size_t FREELIST_INDEX(size_t bytes)
	{
		return (((bytes)+__ALIGN - 1) / __ALIGN - 1);
	}

	// start_free与end_free用来标记内存池中大块内存的起始与末尾位置
	static char *start_free;
	static char *end_free;

	// 用来记录该空间配置器已经想系统索要了多少的内存块
	static size_t heap_size;

	// ...
}

申请内的代码;

// 函数功能:向空间配置器索要空间
// 参数n: 用户所需空间字节数
// 返回值:返回空间的首地址
static void * allocate(size_t n) {
	obj * __VOLATILE * my_free_list;
	obj * __RESTRICT result;
	// 检测用户所需空间释放超过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)
	{
		// 将n向上对齐到8的整数被,保证向桶中补充内存块时,内存块一定是8的整数倍
		void *r = refill(ROUND_UP(n));
		return r;
	}

	// 维护桶中剩余内存块的链式关系
	*my_free_list = result->free_list_link;
	return (result);
};

在上面的流程图中,我们看到还有一个内存填充的过程;

具体的填充流程如下

填充实例代码如下:

// 函数功能:向哈希桶中补充空间
// 参数n:小块内存字节数
// 返回值:首个小块内存的首地址
template <int inst>
void* __default_alloc_template<inst>::refill(size_t n) {
	// 一次性向内存池索要20个n字节的小块内存
	int nobjs = 20;
	char * chunk = chunk_alloc(n, nobjs);

	obj ** my_free_list;
	obj *result;
	obj *current_obj, *next_obj;
	int i;
	// 如果只要了一块,直接返回给用户使用
	if (1 == nobjs)
		return(chunk);

	// 找到对应的桶号
	my_free_list = free_list + FREELIST_INDEX(n);
	// 将第一块返回值用户,其他块连接在对应的桶中
	// 注:此处代码逻辑比较简单,但标准库实现稍微有点复杂
	result = (obj *)chunk;
	*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)
		{
			current_obj->free_list_link = 0;
			break;
		}
		else
		{
			current_obj->free_list_link = next_obj;
		}
	}

	return(result);
}

当然,内存不是想申请就可以得到的,那如果没有足够的内存时会怎样?

请看下面的流程图

期中后颜色越深,表示内存申请走的步骤越多。

好了,讲到这里,1,2级的内存配置器已经讲完了;接下来让我们再看看空间配置器中的空间回收

 

首先请看流程图!

回收实例代码

// 函数功能:用户将空间归还给空间配置器
// 参数:p空间首地址 n空间总大小
static void deallocate(void *p, size_t n) {

	obj *q = (obj *)p;
	obj ** my_free_list;

	// 如果空间不是小块内存,交给一级空间配置器回收
	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;
}

好了,以上就是本次介绍的全部内容了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值