259-C++ STL SGI标准的空间配置器alloc

1.volatile关键字

2.一开始时,自由链free_list都为空,当链表为空时,要进行填充,调用refill函数进行填充

3.内存池只能解决外部碎片,但是不能解决内部碎片,假如我需要申请20字节的空间,那么只能将24字节的内存分配给我,其中20字节供我使用,剩下的4字节就浪费了,就是块内碎片,剩下的4个字节是不会链接到其他区域的链表上的,因为最小内存块的大小是8字节

内存碎片是无法克服的问题,理论上外部碎片和内碎片是无法同时解决的

4.在自由链中的这些内存块还是属于内存池,而不属于用户,所以内存池可以自行调整

5.我们希望一下子可以填充20个块,如果成功申请下来了20个块,其中一块供用户使用,剩下19个块链接到自由链对应的区域中

如果申请下来了不够20个块,但是大于一个块也行,其中第一块供用户使用,其他块链接到自由链中

6.模拟申请空间时空间不足时一级配置器的处理方式

首先营造一个在申请空间之前,已经从堆区申请了很大的一块内存,然后此时调用自己命名空间的yhp::malloc_alloc::allocate函数

char* cpa = (char*)malloc(sizeof(char) * 100000000);
void fun()
{
	if (cpa != nullptr)
	{
		free(cpa);
	}
	cpa = nullptr;
	yhp::malloc_alloc::set_malloc_handler(nullptr);
}
int main()
{
	yhp::malloc_alloc::set_malloc_handler(fun);
	int* p = (int*)yhp::malloc_alloc::allocate(sizeof(int) * 100);
	return 0;
}

首先调用PFUN set_malloc_handler(PFUN p)函数,__malloc_alloc_oom_handler指向fun函数,然后调用一级配置器中的allocate函数,实际上系统仍然可以分配我们需要的空间,但是为了演示内存不足的情况,我将result的值置为0,来表明空间不足以分配了
在这里插入图片描述
此时,申请空间失败了,调用oom_malloc函数来继续申请空间,由于__malloc_alloc_oom_handler不为空(指向fun函数),所以my_malloc_handler也不为空,所以会调用my_malloc_handler()函数,也就是fun函数,将cpa指向的空间释放掉,然后调用set_malloc_handler函数,将__malloc_alloc_oom_handler倒换成nullptr,这就在以后如果申请空间不足时,不能再调用fun函数释放空间了,因为之前已经释放过了,然后result = malloc(n);继续申请空间,如果申请成功了,返回申请的空间

假如我在调用完fun函数释放完空间以后,仍然不足够我开辟空间,那么会再次进入for循环,此时my_malloc_handler==nullptr,调用宏__THROW_BAD_ALLOC抛出异常或者终止程序

7.模拟二级配置器申请空间时的情况

int main()
{
	yhp::my_list<int> mylist;
	return 0;
}

先调用mylist的构造函数,然后调用_Buynode函数,然后调用data_allocate::allocate函数,也就是二级配置器的allocate函数,来申请1个T类型的空间,T是一个_Node类型,在重定义data_allocate的时候设置的,然后调用一级配置器的allocate函数,此时size的大小是12字节,12小于128字节,就用二级配置器,如果大于128字节,就调用一级配置器,然后调用FREELIST_INDEX获取几号区域的下标,my_free_list指向下标为1的区域(1区域一个内存块为16个字节),result指向一号区域的第一个内存块,由于此时自由链中还没有内存块,所以都为NULL
在这里插入图片描述
然后进行填充,填充之前调用ROUND_UP函数,将12向上取整为8的倍数,即16,然后调用refill函数填充,一次填充20个块,然后调用chunk_alloc函数,总共需要的字节数total_bytes为2016=320个字节,此时池子剩余的字节数bytes_left为0,无法分配,执行size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);希望获取了一个3202=640字节的空间bytes_to_get,start_free指向新空间的开头,end_free = start_free + bytes_to_get;end_free指向新空间的末尾,此时内存池的大小heap_size为640字节,再调用chunk_alloc函数,此时640大于需要开辟的320,result指向池子的开头,start_free = start_free + total_bytes;start_free指向池子的中间,这一块就是申请的空间,然后回到refill函数,此时chunk指向这块空间,my_free_list指向一号区域,next_obj指向的就是第二个内存块(一共1号区域分了20个内存块),*my_free_list也指向第二个内存块,然后将19个内存块以此链接到1号区域中,result指向供给用户使用的内存块,refill填充函数结束时,将result返回
在这里插入图片描述
回到_Buynode函数中,此时_S就是指向用户使用的内存块,然后返回到main函数中,结束

8.如果_Node的节点大小大于128,则直接调用一级配置器

9.一二级配置器代码及注释

#ifndef MY_ALLOC_H
#define MY_ALLOC_H
#include<iostream>
//配置器
namespace yhp
{
#if 0
#include<new>
#define __THROW_BAD_ALLOC throw std::bad_alloc;
#elif !defined(__THROW_BAD_ALLOC)
#define __THROW_BAD_ALLOC std::cerr<<"out of memory"<<std::endl; exit(1)
#endif

	//一级空间配置器
	template<int inst>
	class __malloc_alloc_template
	{
	public:
		using PFUN = void (*)();//PFUN是一个函数指针
	private:
		//当调用allocate申请空间内存不足时,调用oom_malloc
		static void* oom_malloc(size_t n)
		{
			void* result = NULL;
			void (*my_malloc_handler) () = nullptr;
			for (;;)
			{
				my_malloc_handler = __malloc_alloc_oom_handler;
				if (nullptr == my_malloc_handler)//如果没有空间了
				{
					__THROW_BAD_ALLOC;//调用宏,抛出异常或者终止掉
				}
				my_malloc_handler();//释放出更多的内存
				result = malloc(n);//再一次申请空间
				if (nullptr != result)//申请成功了,返回申请空间的指针
				{
					return result;
				}
			}
		}
		//当调用reallocate扩容,空间不足时,调用oom_realloc
		static void* oom_realloc(void* p, size_t new_sz)
		{
			void* result = NULL;
			void (*my_malloc_handler) () = nullptr;
			for (;;)
			{
				my_malloc_handler = __malloc_alloc_oom_handler;//如果set_malloc_handler传进来的指针不为空,
				                                    //说明还有空间
				if (nullptr == my_malloc_handler)
				{
					__THROW_BAD_ALLOC; //调用宏,抛出异常或者终止程序
				}
				my_malloc_handler();//释放出更多的内存
				result = realloc(p, new_sz);//再一次申请空间
				if (nullptr != result)//申请成功了,返回申请空间的指针
				{
					return result;
				}
			}
		}
		//主要解决内存不足的问题
		static PFUN __malloc_alloc_oom_handler;
	public:
		//申请n个空间,不管是不是大于128,都不关心
		static void* allocate(size_t n)
		{
			void* result = malloc(n);//申请n个空间
			if (nullptr == result)//如果申请失败了
			{
				result = oom_malloc(n);//调用oom_malloc
			}
			return result;//如果没有失败就返回申请的空间的指针
		}
		//释放空间
		static void deallocate(void* p, size_t n)
		{
			free(p);
		}
		//实际上size_t old_sz这个参数并没有用到,reallocate用来扩容
		static void* reallocate(void* p, size_t old_sz, size_t new_sz)
		{
			void* result = realloc(p, new_sz);//申请空间
			if (nullptr == result)//申请空间失败了
			{
				result = oom_realloc(p, new_sz);//调用oom_realloc,重新获取空间
			}
			return result;
		}
		//处理内存不足的情况
		//传进来一个函数指针p,p指向的函数可以释放之前申请的内存,并重新调用set_malloc_handler(nullptr)这个函数
		//此时传进来为nullptr,表明之前申请的内存已经释放了
		static  PFUN set_malloc_handler(PFUN p)//进行了数据的倒换
		{
			PFUN old = __malloc_alloc_oom_handler;
			__malloc_alloc_oom_handler = p;
			return old;
		}
	};
	//初始化__malloc_alloc_oom_handler指针
	template<int inst>
	typename __malloc_alloc_template<inst>::PFUN
		__malloc_alloc_template<inst>::__malloc_alloc_oom_handler = nullptr;
	//一级配置器类型重命名
	//typedef __malloc_alloc_template<0> malloc_alloc;
	using malloc_alloc = __malloc_alloc_template<0>;//给这个0就意味着我们要实例化出这个类型





	//二级配置器
	enum { __ALIGN = 8 };
	enum { __MAX_BYTES = 128 };
	enum { __NFREELISTS = __MAX_BYTES / __ALIGN }; // 16

	template<bool threads, int inst>
	class __default_alloc_template
	{
	private:
		union obj
		{
			union obj* free_list_link; // 相当于next域
			//char client_data[1];
		};
	private:
		static obj* volatile free_list[__NFREELISTS];//0~15号链表数组
		static char* start_free;//内存池开始
		static char* end_free;//内存池结束
		static size_t heap_size; //从堆区获取的总空间的大小

		//向上取整到8的倍数
		static size_t ROUND_UP(size_t bytes)
		{
			return (bytes + __ALIGN - 1) & ~(__ALIGN - 1);//__ALIGN - 1设是7,7的二进制是0000 0111,取反是1111 1000
														//1111 1000和(bytes + __ALIGN - 1)按位与的结果就是向上取整到8的倍数
		}

		//得到0~15号链表的下标
		static size_t FREELIST_INDEX(size_t bytes)
		{
			return (bytes + __ALIGN - 1) / __ALIGN - 1;
		}

		static char* chunk_alloc(size_t size, int& nobjs)
		{
			char* result = NULL;
			size_t total_bytes = size * 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 >= size)//池子的容量不能全部满足,但是能满足一部分
				//能不能分一个块?如果比一个块要大,分几个块也行,不一定就是20个块
			{
				nobjs = bytes_left / size;//此时nobjs就是分的块的个数
				total_bytes = size * nobjs;//需要的总大小
				result = start_free;//指向池子的开始
				start_free = start_free + total_bytes;//指向池子某一个位置
				return result;//返回指向池子开头的指针
			}
			else//内存池连一个块都无法分配,但是此时bytes_left肯定是8的倍数
			{
				size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);//希望获得的空间是原来的2倍并加上一个调整值
				if (bytes_left > 0)//先将将剩余的内存链接到对应的内存块中
				{
					obj* volatile* my_free_list = free_list + FREELIST_INDEX(bytes_left);
					((obj*)start_free)->free_list_link = *my_free_list;
					*my_free_list = (obj*)start_free;
				}
				//处理完剩余的内存后,再从堆区申请空间
				start_free = (char*)malloc(bytes_to_get);//start_free指向新开辟的空间
				if (NULL == start_free)//申请为空,没有从堆区申请到空间
				{
					//堆区没有空间了,依次看后面的区域有没有空间
					obj* volatile* my_free_list = NULL;
					obj* p = NULL;
					for (int i = size; i <= __MAX_BYTES; i += __ALIGN)
					{
						my_free_list = free_list + FREELIST_INDEX(i);
						p = *my_free_list;//指向FREELIST_INDEX(i)区域的第一个节点
						if (NULL != p)//找到了
						{
							*my_free_list = p->free_list_link;//该区域指针my_free_list指向第二个节点
							start_free = (char*)p;//指向申请出来的节点的开头
							end_free = start_free + i;//指向申请出来的节点的尾部
							return chunk_alloc(size, nobjs);//再次调用chunk_alloc函数
						}
					}
					//如果走到这里说明堆区申请不出来了,自由链也没有了,都为空,走头无路了,就调用一级配置器
					//如果说my_malloc_handler不为空,就调用释放空间函数,如果my_malloc_handler也为空,说明是真的走投无路了
					//要么退出程序,要么提示out of memory
					start_free = (char*)malloc_alloc::allocate(bytes_to_get);
				}
				//不等于空,说明从堆区获得了空间
				end_free = start_free + bytes_to_get;//end_free指向新开辟空间的某个位置
				heap_size += bytes_to_get;//新从堆区获得的大小
				return chunk_alloc(size, nobjs);//继续进入该函数
			}
		}

		static void* refill(size_t size)//size是此时是8的倍数
		{
			int nobjs = 20;//填充20个块
			char* chunk = chunk_alloc(size, nobjs);//注意nobjs是以引用的方式传进去的
			if (1 == nobjs)//如果只给了一个内存块,也够用,就供给用户使用
			{
				return chunk;
			}
			//核心代码
			obj* volatile* my_free_list = NULL;
			obj* result = (obj*)chunk;//块指针
			obj* current_obj = NULL;
			obj* next_obj = NULL;
			int i = 0;
			my_free_list = free_list + FREELIST_INDEX(size);//my_free_list指向FREELIST_INDEX(size)号链表的开头
			*my_free_list = next_obj = (obj*)(chunk + size);//*my_free_list和next_obj都指向第二个节点的开始位置
			for (i = 1; ; ++i)//将nobjs-1块内存链接进去
			{
				current_obj = next_obj;//current_obj指向next_obj所指向的位置
				next_obj = (obj*)((char*)next_obj + size);//next_obj指向下一个块的开始位置
				//这样一来current_obj指向上个节点的开始位置,next_obj指向本节点的开始位置
				if (i == nobjs - 1)//说明是最后一块
				{
					current_obj->free_list_link = NULL;
					break;
				}
				current_obj->free_list_link = next_obj;
			}
			return result;
		}
	public:
		//申请空间
		static void* allocate(size_t size)
		{
			if (size > (size_t)__MAX_BYTES)//如果大于128字节
			{
				return malloc_alloc::allocate(size);//调用一级配置器的allocate函数
			}
			//小于等于128
			obj* result = nullptr;
			obj* volatile* my_free_list = nullptr; //二级指针
			my_free_list = free_list + FREELIST_INDEX(size);//my_free_list指向某号链表的开头
			result = *my_free_list;//result指向size对应区域的第一个内存块,就是供用户使用的内存块
			if (nullptr == result)//如果没有内存块,即内存不足
			{
				void* r = refill(ROUND_UP(size));//填充链表
				return r;
			}
			//如果还有内存块,就分配
			*my_free_list = result->free_list_link;//*my_free_list指向下一块内存
			return result;//返回第一块内存
		}
		//销毁,将p指向的空间还给内存池,n是p所指向的空间的大小
		static void deallocate(void* p, size_t n)
		{
			//如果大于128,转到一级配置器
			if (n > (size_t)__MAX_BYTES)
			{
				malloc_alloc::deallocate(p, n);
				return;
			}
			//小于等于128,二级配置器释放
			obj* q = (obj*)p;
			obj* volatile* my_free_list = free_list+FREELIST_INDEX(n); //二级指针
			q->free_list_link = *my_free_list;
			*my_free_list = q;
			return;
		}
		//非要重要,面试经常考,
		static void* reallocate(void* p, size_t old_sz, size_t new_sz)
		{
			//如果旧的大小和新的大小都大于128,就转到一级配置器去
			if (old_sz > (size_t)__MAX_BYTES && new_sz > (size_t)__MAX_BYTES)
			{
				return malloc_alloc::reallocate(p, old_sz, new_sz);
			}
			//如果原来的大小和新的大小的区域是一个,不需要再开辟空间了,比如20和22
			if (ROUND_UP(old_sz) == ROUND_UP(new_sz))//将旧的大小和新的大小提升到8的倍数
			{
				return p;
			}
			size_t sz = old_sz < new_sz ? old_sz : new_sz;//保存较小的空间大小
			void* s = allocate(new_sz);//开辟新的空间
			memove(s, p, sz);//将原来的空间的数据拷贝到新空间中
			deallocate(p, old_sz);//释放原来的空间
			return s;
		}
	};
	template<bool threads, int inst>
	typename __default_alloc_template<threads, inst>::obj* volatile
		__default_alloc_template<threads, inst>::free_list[__NFREELISTS] = {};

	template<bool threads, int inst>
	char* __default_alloc_template<threads, inst>::start_free = nullptr;

	template<bool threads, int inst>
	char* __default_alloc_template<threads, inst>::end_free = nullptr;

	template<bool threads, int inst>
	size_t __default_alloc_template<threads, inst>::heap_size = 0;

	/
	// 开关语句
	//如果没有定义__USE_MALLOC,那么默认使用二级配置器
	//如果定义了__USE_MALLOC,就是用一级配置器
#ifdef __USE_MALLOC
	typedef __malloc_alloc_template<0> malloc_alloc
		typedef malloc_alloc_alloc;
#else
	typedef __default_alloc_template<0, 0> alloc;
#endif

	template<class T, class Alloc>
	class simple_alloc
	{
	public:
		//申请n个T类型空间
		static T* allocate(size_t n)//申请n个T类型的空间大小
		{
			return (T*)Alloc::allocate(sizeof(T) * n);
		}
		//申请1个T类型空间
		static T* allocate()
		{
			return (T*)Alloc::allocate(sizeof(T));
		}
		//释放n个T类型空间
		static void deallocate(T* p, size_t n)
		{
			if (NULL == p) return;
			Alloc::deallocate(p, sizeof(T) * n);
		}
		//释放1个T类型空间
		static void deallocate(T* p)
		{
			if (NULL == p) return;
			Alloc::deallocate(p, sizeof(T));
		}
	};
}
#endif
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值