C++内存管理(1)alloc运行一瞥

alloc源码剖析

该配置器设计了双层级别:
	一级配置器:直接使用malloc、free、以及对内存申请失败的处理;
	二级配置器:将采用复杂的内存池来管理  

一级配置器实现:

当请求大于128bytes时,使用该分配器

二级配置器实现:

alloc 维护16条链表,每条链表管理不同大小的区块,最小的为8bytes,最大为128bytes。

链表采用了嵌入式指针,定义如下:

union obj{
		union obj *next;
		char client[1];
	}; 

obj指针既可以看作一个指针,指向一个obj对象,也可以一个指针,指向实际的区块,一物二用。

二级配置器头文件整体代码:

namespace TinySTL{

/*
**空间配置器,以字节数为单位分配
**内部使用
*/
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{
		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:
	//将bytes上调至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);
};
}  

ROUND_UP:内存对齐,将大小调整为8的倍数,如果申请的大小为5,则调整为8

ROUND_UP的设计十分巧妙 
一个数k如果正好是2^n的倍数,那么用二进制来 表示的话,低n位必定全是0,而 _ALIGN-1的二进制表示则是低n位全为1,
那么k+ _ALIGN-1和~(_ALIGN – 1)取与必定是k本身;如果一个数k不是2^n的倍数,则必有m*2^n<k<(m+1)*2^n,
要做的就是求出(m+1)*2^n, 则k用二进制来表示的话,低n位肯定不全为0,再加上_ALIGN-1必然会向第n+1位进位,此时若
将低n位全部置零的话,得到刚好就是 (m+1)*2^n,而和~(_ALIGN – 1)取与正好达到这一效果。
这里的关键在于,对一个数如果有m*2^n<k<(m+1)*2^n,那么有 k=m*2^n+p, 且p<2^n,这样k就分成了两部分,
在用二进制表示的时候,p是存储在低n位中,而m*2^n是存储在高位中(即除去低n位的剩余位数),
P+ALIGN-1,肯定向高位进一,即为(m+1)*2^n+P1,然后与全0,则得到(m+1)*2^n=2^(n+1)。 

二级配置器源文件整体代码:

namespace TinySTL{

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

void *alloc::allocate(size_t bytes)
{
	if (bytes > EMaxBytes::MAXBYTES){
		return malloc(bytes);
	}
	size_t index = FREELIST_INDEX(bytes);
	obj *list = free_list[index];
	if (list){//此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{
		size_t index = FREELIST_INDEX(bytes);
		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);
	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);
		*my_free_list = next_obj = (obj *)(chunk + bytes);
		//将取出的多余的空间加入到相应的free list里面去
		for (int i = 1;; ++i){
			current_obj = next_obj;
			next_obj = (obj *)((char *)next_obj + bytes);
			if (nobjs - 1 == i){
				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{//内存池剩余空间连一个区块的大小都无法提供
		size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
		if (bytes_left > 0){
			obj **my_free_list = free_list + FREELIST_INDEX(bytes_left);
			((obj *)start_free)->next = *my_free_list;
			*my_free_list = (obj *)start_free;
		}
		start_free = (char *)malloc(bytes_to_get);
		if (!start_free){
			obj **my_free_list = 0, *p = 0;
			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);
	}
}
}

alloc运行一瞥:

end_free - start_free 大小为战备池pool的大小 
图片中的累计申请量RoundUp为上面代码中的heap_size 

第一次申请时,申请大小为32bytes,3号链表未悬挂区块,向pool要求内存,pool大小为0,所以调用malloc申请一块20 * 2 * 32 bytes,留一半放入pool中,返回一块区块给客户.剩下的640bytes放入pool中(该次申请含有cookie)

第二次申请时,申请大小为64bytes,3号链表未悬挂区块,由于pool够切割出一块,直接返回一块给客户,剩余的挂到第7号链表(本次申请不含cookie),pool的容量清零。

第三次申请原理同第一次,该次申请含cookie。pool剩余2000

第四次申请原理同第二次,该次申请不含cookie。pool剩余240(pool最多切出20块,剩余的为本次的内存碎片,由下次分配处理)

第五次连续申请三次88bytes,由于链表悬挂了区块所以直接取出返回给客户,无cookie

第六次申请同第第四次,pool剩余80,不含cookie

第七次申请104bytes,12号链表未悬挂区块,pool不够切割出一块,则将pool放到匹配的链表上(pool大小为80,则将该区块放到第9条链表),再malloc申请内存,含cookie

系统内存分配完,malloc失败时措施

posted @ 2022-09-19 19:34  WetYu  阅读(18)  评论(0)    收藏  举报