SGI STL空间配置器

空间配置器

       STL的空间配置器是为整个STL的容器中存放的操作对象(所有的数值)分配空间的一个组件,之所以说是空间配置器而不是内存配置器是因为空间不一定是内存,也可以是硬盘或其他存储介质。本文所介绍的SGI STL提供的配置器配置的对象是内存。
 

SGI空间配置器——std::alloc

       我们熟悉的C++的动态内存分配与释放函数是new和delete,它们配置内存和释放内存的操作都含有两阶段的操作。new配置内存时,先为对象开辟内存空间,然后构造对象;而delete操作则是先析构对象再释放内存空间。
     为了精密分工,STL allocator将这两阶段的操作分开定义:内存配置操作由alloc::allocate()负责,内存释放操作则由alloc::deallocate()负责;对象构造操作由::construct()负责,对象析构操作由::destroy()负责。
       空间配置与释放
       SGI STL对空间配置与释放的设计如下:
·向system heap请求空间
·考虑多线程状态
·考虑内存不足时的应变措施
·考虑过多“小型区块”可能造成的内存碎片问题

       SGI STL利用C中的原生函数malloc()和free()来完成内存的配置与释放。考虑到小型区块可能造成的内存破碎问题,SGI设计了双层级配置器。第一级配置器直接使用malloc()和free(),第二级配置器则视不同情况采用不同的策略:当配置区块超过128Bytes时,调用第一级配置器;当配置区块小于128Bytes时,为了降低额外负担,便采用内存池的资源管理方式。

SGI STL空间配置器_第1张图片

第一级配置器__malloc_alloc_template

       第一级配置器很简单,__malloc_alloc_template的模板声明如下:

template 
class __malloc_alloc_template {
private:
//以下函数用来处理内存不足的情况
	static void *oom_malloc(size_t);//allocate函数分配内存失败后调用此函数
	static void *oom_realloc(void *, size_t);//reallocate函数分配内存失败后调用此函数
	static void (* __malloc_alloc_oom_handler) ();

public:
	static void* allocate(size_t n);//内存配置函数,调用malloc()
	static void deallocate(void *p, size_t /* n */);//内存释放函数,调用free()

	static void *reallocate(void *p, size_t /* old_sz */, size_t new_sz);//配置新内存,直接调用C中的realloc()

	static void (*set_malloc_handler(void (*f)())) ();//仿真C++的set_new_handler()内存不足处理例程
};

       一级配置器的内存配置与释放函数都直接调用了C中的相关函数,设计简单。而当内存配置函数分配内存失败时,就会调用相应的函数来处理内存不足的情况。其中oom_malloc和oom_realloc函数体内都设计了一个无限循环体,不断调用“内存不足处理例程”,期望在某次调用后获得足够的内存完成任务。但如果“内存不足处理例程”并未被设定,则会抛出__TROW_BAD_ALLOC异常,终止程序。

第二级配置器__default_alloc_template

       二级配置器的做法是,如果区块超过128Bytes时,就移交一级配置器处理,否则以内存池管理。内存池是一大块内存,其中细分了16种不同大小的区块(8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128Bytes),由16个free-lists管理。free-lists并不需要单独的空间存放指向下一节点的指针,free-lists的节点结构如下:

union obj {
	union obj* free_list_link;
	char client_data[1];
};

       指向下一节点的指针和指向实际区块的指针共享内存。

       free-lists其实是一个存放着16个指针的数组,每个数组元素中的指针都指向一个链表,该链表把同样大小的区块链接在一起。

SGI STL空间配置器_第2张图片

       二级配置器__default_malloc_template的模板声明如下:

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

template 
class __default_alloc_template {
private:
	//将bypes上调至8的倍数
	static size_t ROUND_UP(size_t bytes) {
		return (((bytes) + __ALIGN - 1) &~ (__ALIGN - 1));
	}
	union obj {
		union obj* free_list_link;
		char client_data[1];
	};
	static obj *volatile free_list[__NFREELISTS];//16个free-lists
	//根据区块大小选择第几号free-list
	static size_t FREELIST_INDEX(size_t bytes) {
		return (((bytes) + __ALIGN - 1)/__ALIGN -1);
	};
	static void *refill(size_t n);
	static char *chunk_alloc(size_t size, in &nobjs);//配置一大块空间,可容纳nobjs个大小为size的区块,若配置nobjs个区块有所不便,nobjs可能会降低

	static char *start_free;//内存池起始位置,只在chunk_alloc()中改变
	static char *end_free;//内存池结束位置,只在chunk_alloc()中改变
	static size_t heap_size;//system heap的大小
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);//重新分配空间
};

 

空间配置函数allocate()

 

       二级配置器的allocate函数首先判断区块大小,大于128Bytes就调用一级配置器,小于128Bytes就检查对应的free-lists。如果free-lists内有可用的区块,就直接拿来用,若没有则将区块大小上调至8倍数边界,然后调用refill()为free list重新填充空间。

 

static void *allocate(size_t n) {
	obj *volatile *my_free_list;//指向free_list的指针
	obj *result;

	//区块大于128Bytes就调用一级配置器
	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);
}

SGI STL空间配置器_第3张图片

 

空间释放函数deallocate()

 

static void deallocate(void *p, size_t n) {
	obj*q = (obj *)p;
	obj *volatile *my_free_list;//指向free_list的指针


	//大于128Bytes就调用一级配置器
	if (n > (size_t)MAX_BYTES) {
		malloc_alloc::deallocate(p, n);
	}
	//寻找对应的free list
	my_free_list = free_list + FREELIST_INDEX(n);
	//调整free list,回收区块
	q->free_list_link = *my_free_lisk;
	*my_free_list = q;
}

SGI STL空间配置器_第4张图片

重新填充free lists

template 
void *__default_alloc_template::refill(size_t n) {
	int nobjs = 20;//默认填充20个大小为size_t的空间
	//调用chunk_alloc(),尝试取得nogjs个区块作为free list的新节点
	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_INDEX(n);//找到合适的区块链表
	//引导free list指向新配置的空间
	*my_free_list = next_obj = (obj *)(chunk + n);
	//将新分配得到的节点串接起来
	for (i = 1; ; i++) { 第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);
}

内存池(memory pool)

template 
char *__default_alloc_template::chunk_alloc(size_t size, int &nobjs) {
	char *result;
	size_t total_bypes = size*nobjs;
	size_t bytes_left = end_free-start_free;//内存池剩余空间

	if (bytes_left >= total_bypes) { //内存池剩余空间满足需求量
		result = start_free;
		start_free += total_bytes;//调整内存池起始位置
		return (result);
	}
	else if (bytes_left >= size) { //内存池剩余空间不能完全满足需求量,但足够供应至少一个区块
		nobjs = bytes_left/size;//计算能供应多少个区块,调整nobjs的值
		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);//准备向system heap申请空间,大小为需求量的两倍加上一个随配置次数增加而增加的附加量
		if (bytes_left > 0) { //将内存池剩余的零头配给适当的free list
			obj *volatile *my_free_list = free_list + FREELIST_INDEX(bytes_left);//寻找适合的区块链表
			//调整free list,将剩余空间编入
			((obj *)start_free)->free_list_link = *my_free_list;
			*my_free_start = (obj )start_free;
		}
		//配置heap空间,用来补充内存池
		start_free = (char *)malloc(bytes_to_get);
		if (0 == start_free) { //heap空间不足
			int i;
			obj *volatile *my_free_list, *p;
			//查找free-lists中是否有未被使用且足够大的区块,若有则归还到内存池
			for (i = size; i <= __MAX_BYTES; i += __ALIGN) { //从所需大小的区块查找,找不到则移动到更大区块的节点中查找
				my_free_list = free_list + FREELIST_INDEX(i);
				p = *my_free_list;
				if (0 != p) { //当前查找的区块链中尚有未用区块
					//释放出未用区块给内存池
					*my_free_list = p->free_list_link;
					start_free = (char *)p;//调整内存池起始位置
					end_free = start_free + i;//调整内存池终止位置
					return (chunk_alloc(size, nobjs));//递归调用,为了调整nobjs的值
					//任何残余零头都将被编入适当的free-list中备用
				}
			}
		//如果所有方法都无法空出内存,则调用一级配置器
		end_free = 0;
		start_free = (char *)malloc_alloc::allocate(bytes_to_get);
		}
		heap_size += bytes_to_get;
		end_free = start_free + byte_to_get;
		//递归调用,修正nobjs
		return (chunk_alloc(size, nobjs));
	}
}

       chunk_alloc()函数以end_free-start_free来监控内存池的内存剩余量。如果内存充足,则直接调出20个要求大小的区块返回给free list;如果内存不足以提供20个,但足以提供至少一个区块,就调出实际可用区块给free list;当内存池容量连一个区块都无法提供时,则将内存池中剩余的零头分给适合大小的free list,然后再向system heap申请补充内存,申请的大小为当前需求量的两倍加上一个随着配置次数增加而增加的附加量;如果heap空间不足,则在free list中寻找是否有足够大小且未被使用的区块补充到内存池中;若上述所有方法都无法分配到内存,则会调用一级配置器来尝试给内存池分配空间。

SGI STL空间配置器_第5张图片

                                                                                                      本文部分内容摘自《STL源码剖析》,有改动

你可能感兴趣的:(c/c++,学习笔记)