(第十四步) STL: alloc内存配置器解析

对于创建自己的STL库来说,重点就在于alloc配置器,其他大部分还是好理解的

alloc 配置器

std::alloc在源码中考虑小型区块的碎片化问题,存在两级配置器:

  • 第一级配置器用来申请超过内存块容量(128byte)的内存,并管理
  • 第二级的作用是完成std::alloc对内存的分配。

一级配置器主要在于mallocfree的应用,就不做多的解释,配置器的精髓都在二级配置器中。

二级配置器

二级空间配置器使用内存池+自由链表的形式避免了小块内存带来的碎片化,提高了分配的效率,提高了利用率。SGI的做法是先判断要开辟的大小是不是大于128,如果大于128则就认为是一块大块内存,调用一级空间配置器直接分配。否则的话就通过内存池来分配,假设要分配8个字节大小的空间,那么他就会去内存池中分配多个8个字节大小的内存块,将多余的挂在自由链表上,下一次再需要8个字节时就去自由链表上取就可以了,如果回收这8个字节的话,直接将它挂在自由链表上就可以了。

为了便于管理,二级空间配置器在分配的时候都是以8的倍数对齐。也就是说二级配置器会将任何小块内存的需求上调到8的倍数处(例如:要7个字节,会给你分配8个字节。要9个字节,会给你16个字节),尽管这样做有内碎片的问题,但是对于我们管理来说却简单了不少。因为这样的话只要维护16个free_list就可以了,free_list这16个结点分别管理大小为8,16,24,32,40,48,56,64,72,80,88,86,96,104,112,120,128字节大小的内存块就行了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MIwoHJAS-1629028258468)(Image_File/alloc 配置器.assets/image-20210815092915468.png)]

自由链表的结点类型:

union obj
{
       union obj* free_list_link;
       char client_data[1];    /* 这个数据部分,新版本取消了,内存分配中也没有用到,可以直接删除      */
};

内存池模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UW9vXxcf-1629028258472)(Image_File/alloc 配置器.assets/image-20210815180335235.png)]

已分配:交给free-list来管理

未分配:为内存池,通过stare_free、end_free来描述

head_size:总开辟空间

注意:

  • 当pool减少时,就把start_free右移;

  • 当pool修改时,就将两根指针指向的位置修改

内存池分配过程

主要函数:

  • allocate:通过free-list分配内存
  • refill:在内存池中取新块
  • chunk_alloc:内存池扩展新块

在这里插入图片描述

图中过程的一些说明:

  1. pool不会在原来的位置上增加,需要更多的pool内存的时候,会把原来pool中的内存视为内存碎片连接到某一个链表上,然后为pool重新分配。
  2. heap_size用于记录由alloc第二级分配器申请的内存的总大小,也就是现在alloc所可以进行管理的内存的总大小,用于后边计算追加量。

alloc.h

/*
* alloc.h文件是项目的开始部分,无论是哪一个容器,都需要分配内存才能行动,
* alloc.h文件也是内存分配中的最基石
* 
* 第一步
*/


#ifndef _ALLOC_H_
#define _ALLOC_H_

#include <cstdlib>

namespace mySTL{

	/*
	**空间配置器,以字节数为单位分配
	**内部使用
	*/
	class alloc{
	private:
		enum EAlign{ ALIGN = 8};//小型区块的上调边界
		enum EMaxBytes{ MAXBYTES = 128};//小型区块的上限,超过的区块由malloc分配
		enum ENFreeLists{ NFREELISTS = (EMaxBytes::MAXBYTES / EAlign::ALIGN)};//free-lists的个数
		enum ENObjs{ NOBJS = 20};//每次增加的节点数
	
	private:
		// free-lists的节点构造
		// 利用union节省了空间,这个区块既可以放下一块的指针,又可以存放数据
		// 一级指向另一个obj,二级obj指向实际区块
		union obj{
			union obj *next;
			char client[1];	 /* 这个数据部分,新版本取消了,内存分配中也没有用到,可以直接删除      */
		};

		static obj *free_list[ENFreeLists::NFREELISTS];

	private:
		static char *start_free;	// 内存池起始位置
		static char *end_free;		// 内存池结束位置
		static size_t heap_size;	// 堆的大小,总分配大小
		// 之所以在这里使用静态内存的原因也很清楚,这些数据都是唯一的,\
		// 不可能同时存在两个,一旦改变(一般也不改变)就是全局一起改变

	private:
		// 将byte上调至8的倍数,1-8范围8,9-16返回16……总是8的倍数
		static size_t ROUND_UP(size_t bytes){
			return ((bytes + EAlign::ALIGN - 1) & ~(EAlign::ALIGN - 1));
		}
		// 根据区块大小,决定使用第n号free-list,n从0开始计算
		static size_t FREELIST_INDEX(size_t bytes){
			return (((bytes)+EAlign::ALIGN - 1) / EAlign::ALIGN - 1);
		}
		// 返回大小为n的对象,并可能加入大小为n的其他区块到free-list
		static void *refill(size_t n);

		//配置一大块空间,可容纳nobjs个大小为size的区块
		//如果配置nobjs个区块有所不便,nobjs可能会降低 
		static char *chunk_alloc(size_t size, size_t& nobjs);

	public:
		// 外部可以调用的部分
		// 静态方法效率上比实例化要高,这种经常要用的函数静态是最好了,每次实例化都要成本

		static void *allocate(size_t bytes);
		static void deallocate(void *ptr, size_t bytes);
		static void *reallocate(void *ptr, size_t old_sz, size_t new_sz);
	};
}

#endif

alloc.cpp

#include "../Includes/Alloc.h"

namespace mySTL {

	// 初始化变量
	char* alloc::start_free = 0;
	char* alloc::end_free = 0;
	size_t alloc::heap_size = 0;

	alloc::obj* alloc::free_list[alloc::ENFreeLists::NFREELISTS] = {
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	};	// 16个

	void* alloc::allocate(size_t bytes) {
		if (bytes > EMaxBytes::MAXBYTES) {
			return malloc(bytes);// >128 使用一级配置器
		}
		// 寻找16个中较为合适的一个
		size_t index = FREELIST_INDEX(bytes);
		obj* list = free_list[index];

		if (list != 0) {//此list还有空间给我们
			free_list[index] = list->next; // 把后一块内存的地址存入,便于下次取用
			return list;  //如果这块内存恰好空着,把这块内存送出
		}
		else {//此list没有足够的空间,需要从内存池里面取空间
			return refill(ROUND_UP(bytes));
		}
	}

	void alloc::deallocate(void* ptr, size_t bytes) {
		if (bytes > EMaxBytes::MAXBYTES) {
			free(ptr);
		}
		else {
			// 寻找对应的free-list
			size_t index = FREELIST_INDEX(bytes);
			// 调整free-list ,回收区块
			obj* node = static_cast<obj*>(ptr);
			node->next = free_list[index]; 
			free_list[index] = node;	// 将这块内存放在最前方,之前的内存挂在其后
		}
	}

	void* alloc::reallocate(void* ptr, size_t old_sz, size_t new_sz) {
		deallocate(ptr, old_sz);//销毁
		ptr = allocate(new_sz);//重分配

		return ptr;
	}

	//返回一个大小为n的对象,并且有时候会为适当的free list增加节点
	//假设bytes已经上调为8的倍数
	void* alloc::refill(size_t bytes) {
		size_t nobjs = ENObjs::NOBJS; // 每次增加节点数
		//从内存池里取
		char* chunk = chunk_alloc(bytes, nobjs); // 从内存池中,取nobjs个新区块

		obj** my_free_list = 0;
		obj* result = 0;
		obj* current_obj = 0, * next_obj = 0;

		if (nobjs == 1) 
		{
			//只够一个对象用,无需分割直接返回
			return chunk;
		}
		else 
		{
			my_free_list = free_list + FREELIST_INDEX(bytes);
			result = (obj*)(chunk);
			// 将取出的多余的空间加入到相应的free list里面去(取自内存池)
			*my_free_list = next_obj = (obj*)(chunk + bytes);
			
			// 将free_list各节点串起来
			// 从1开始,因为第0个将返回给客端
			for (int i = 1;; ++i) 
			{
				current_obj = next_obj;
				next_obj = (obj*)((char*)next_obj + bytes);

				if (nobjs - 1 == i) 
				{
					//分得块数足够,内存已经切完,最后一块指向0,退出循环
					current_obj->next = 0;
					break;
				}
				else {
					current_obj->next = next_obj;
				}
			}
			return result;
		}
	}

	//假设bytes已经上调为8的倍数,内存池计算
	char* alloc::chunk_alloc(size_t bytes, size_t& nobjs) {
		char* result = 0;
		size_t total_bytes = bytes * 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 >= bytes) {//内存池剩余空间不能完全满足需要,但足够供应一个或以上的区块
			nobjs = bytes_left / bytes;
			total_bytes = nobjs * bytes;
			result = start_free;
			start_free += total_bytes;
			return result;
		}
		else {//内存池剩余空间连一个区块的大小都无法提供
			// 开辟2*20 个byte空间,1个交给客端,19个维护,剩下20个给内存池
			size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);

			if (bytes_left > 0) {
				// 内存池还有参与零头,先给free-list
				obj** my_free_list = free_list + FREELIST_INDEX(bytes_left);
				// 将残余空间编入
				((obj*)start_free)->next = *my_free_list;
				*my_free_list = (obj*)start_free;
			}

			// 配置heap空间补充内存池
			start_free = (char*)malloc(bytes_to_get); // 要一块新的内存,并把内存起点重新定义

			if (0 == start_free) {
				// malloc没有得到内存,heap空间不足
				obj** my_free_list = 0, * p = 0;
				// 寻找尚未使用,且区块足够大的freelist
				for (int i = 0; i <= EMaxBytes::MAXBYTES; i += EAlign::ALIGN) 
				{
					my_free_list = free_list + FREELIST_INDEX(i);
					p = *my_free_list;
					if (p != 0) 
					{ 
						//找到碎片,返回
						*my_free_list = p->next;
						start_free = (char*)p;
						end_free = start_free + i;
						return chunk_alloc(bytes, nobjs);
					}
				}

				end_free = 0;	// 山穷水尽,没有内存可以用,可以试试一级配置器
			}

			heap_size += bytes_to_get; //扩充内存
			end_free = start_free + bytes_to_get; //重定义末尾内存

			return chunk_alloc(bytes, nobjs);
		}
	}
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值