STL源码剖析之stl_alloc(一)

STL主要研究的六大问题:空间配置器,迭代器,容器,仿函数,算法,容器适配器

一级空间配置器源码如下:

#if 0
#include<new>
#define __THROW_BAD_ALLOC throw bad_alloc
#else
#include<iostream.h>
#include<malloc.h>
#include<stdlib.h>
#define __THROW_BAD_ALLOC cerr<<"Out Of Memory."<<endl;exit(1)
#endif

//malloc  free

template<int inst>
class __malloc_alloc_template
{
private:
	//以下都是函式指标,所代表的函式将用来处理内存不足的情况。
	static void* oom_malloc(size_t n)
	{
		void (*my_malloc_handler)();
		void *result;
		for(;;) //不断尝试释放、配置、再释放、再配置…
		{
			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;
		}
	}
	static void* oom_realloc(void *p, size_t n)
	{
		void (* my_malloc_handler)();
		void *result;
		for (;;) //不断尝试释放、配置、再释放、再配置…
		{
			my_malloc_handler = __malloc_alloc_oom_handler;
			if (0 == my_malloc_handler) 
			{
				__THROW_BAD_ALLOC; 
			}
			(*my_malloc_handler)();//呼叫处理例程,企图释放内存。
			result = realloc(p, n);//再次尝试配置内存。
			if (result) 
				return(result);
		}
	}
	static void(*__malloc_alloc_oom_handler)();
public:
	static void* allocate(size_t n)
	{
		void *result = malloc(n);
		//第一级配置器直接使用 malloc()
		//以下,无法满足需求时,改用 oom_malloc()
		if(0 == result)
			result = oom_malloc(n);
		return result;
	}
	static void  deallocate(void *p, size_t n)
	{
		free(p);//第一级配置器直接使用 free()
	}
	static void* reallocate(void *p, size_t old_sz, size_t new_sz)
	{
		void *result = realloc(p, new_sz);
		//第一级配置器直接使用 realloc()
		//以下,无法满足需求时,改用 oom_realloc()
		if(0 == result)
			result = oom_realloc(p, new_sz);
		return result;
	}
public:
	//set_new_handler模拟,可用透过它指定你自己的out_of_memory handler
	static void(*set_malloc_handler(void(*f)()))()
	{
		void(*old)() = __malloc_alloc_oom_handler;
		__malloc_alloc_oom_handler = f;
		return old;
	}
};
// malloc_alloc  out-of-memory handling
//初值为 0。有待客端设定。
void(* __malloc_alloc_template<0>::__malloc_alloc_oom_handler)() = 0;

typedef __malloc_alloc_template<0> malloc_alloc;

第一级配置器以 malloc(), free(), realloc() 等 C函式执行实际的内存配置、释放、重配置动作,并实作出类似 C++ new-handler7 的机制。它不能直接运用 C++ new-handler机制,因为它并非使用 ::operatornew 来配置记忆体。所谓 C++ new handler 机制是,你可以要求系统在内存配置需求无法被满足时,唤起一个你所指定的函式。换句话说一旦 ::operator new 无法达成任务,在丢出std::bad_alloc异常状态之前,会先呼叫由客端指定的处理例程。此处理例程通常即被称为 new-handler。new-handler 解决内存不足的作法有特定的模式,请参考《Effective C++》2e 条款 7。
注意,SGI 以 malloc  而非 ::operator new 来配置内存(我所能够想象的一个原因是历史因素,另一个原因是 C++并未提供相应于 realloc() 的内存配置动作),因此 SGI 不能直接使用 C++的 set_new_handler() ,必须模拟一个类似的 set_malloc_handler() 。
请注意,SGI 第一级配置器的 allocate()  和 realloc() 都是在呼叫 malloc()和 realloc() 不成功后,改呼叫 oom_malloc() 和oom_realloc() 。后两者都有内循环,不断呼叫「内存不足处理例程」,期望在某次呼叫之后,获得足够的内存而圆满达成任务。但如果「内存不足处理例程」并未被客端设定,oom_malloc() 和 oom_realloc()  便老实不客气地呼叫 __THROW_BAD_ALLOC ,
丢出bad_alloc异常讯息,或利用 exit(1) 硬生生中止程序。记住,设计「内存不足处理例程」是客端的责任,设定「内存不足处理例程」也是客端的责任。再一次提醒你,「内存不足处理例程」解决问题的作法有着特定的模式,请参考 [Meyers98]条款 7。

二级空间配置器源码:

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

//以下是第二级配置器。
//注意,无「template 型别参数」,且第二参数完全没派上用场。
//第一参数用于多绪环境下。本书不讨论多绪环境。

template<bool threads, int inst>
class __default_alloc_template
{
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);
protected:
	static void * refill(size_t n);
	static char* chunk_alloc(size_t size, int &nobjs);
private:
	// ROUND_UP()  将 bytes上调至 8的倍数。
	static size_t ROUND_UP(size_t bytes)
	{
		return ((bytes + __ALIGN-1) &~(__ALIGN-1));
	}
private:
	union obj  // free-list s 的节点构造
	{
		union obj * free_list_link;
		char client_data[1];
	};
private:
	// 以下函式根据区块大小,决定使用第 n号  free-list 。n 从 1 起算。
	static size_t  FREELIST_INDEX(size_t bytes)
	{
		return ((bytes + __ALIGN-1) / __ALIGN-1);
	}
	static obj* free_list[__NFREELISTS];
private:
	static char * start_free;
	static char * end_free;
	static size_t heap_size;
};
//以下是 static data member  的定义与初值设定
template<bool threads, int inst>
char* __default_alloc_template<threads, inst>::start_free = 0;
template<bool threads, int inst>
char* __default_alloc_template<threads, inst>::end_free = 0;
template<bool threads, int inst>
size_t __default_alloc_template<threads, inst>::heap_size = 0;

template<bool threads, int inst>
__default_alloc_template<threads, inst>::obj*
__default_alloc_template<threads, inst>::free_list[__NFREELISTS] = 
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

template<bool threads, int inst>
void * __default_alloc_template<threads, inst>::allocate(size_t n)
{
	obj ** my_free_list;
	obj * result;
	if(n > __MAX_BYTES)
		return malloc_alloc::allocate(n);
	
	my_free_list = free_list + FREELIST_INDEX(n);
	result = *my_free_list;
	if(result == 0)
	{
		void *r = refill(ROUND_UP(n));
		return r;
	}
	*my_free_list = result->free_list_link;
	return result;
}
/*记忆池(memory pool)*/
//假设 size  已经适当上调至 8的倍数。
//注意参数 nobjs是 pass by reference 。
template <bool threads, int inst>
char*
__default_alloc_template<threads, inst>::chunk_alloc(size_t size, int& nobjs)
{
    char * result;
    size_t total_bytes = size * nobjs;
    size_t bytes_left = end_free - start_free;// 记忆池剩余空间
	
    if (bytes_left >= total_bytes) 
	{
		// 记忆池剩余空间完全满足需求量。
        result = start_free;
        start_free += total_bytes;
        return(result);
    } 
	else if (bytes_left >= size) 
	{	
		// 记忆池剩余空间不能完全满足需求量,但足够供应一个(含)以上的区块。
        nobjs = bytes_left/size;
        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);
		// 以下试着让记忆池中的残余零头还有利用价值。
		if (bytes_left > 0) {
			// 记忆池内还有一些零头,先配给适当的  free list 。
			// 首先寻找适当的  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_list = (obj *)start_free;
		}
		// 配置 heap 空间,用来挹注记忆池。
		start_free = (char *) malloc (bytes_to_get);
		if (0 == start_free) {
			// heap 空间不足,malloc() 失败。
			int i;
			obj * volatile * my_free_list, *p;
			// 试着检视我们手上拥有的东西。这不会造成伤害。我们不打算尝试配置
			//  较小的区块,因为那在多行程( multi-process )机器上容易导致灾难
			// 以下搜寻适当的  free list ,
			//  所谓适当是指「尚有未用区块,且区块够大」之  free list 。
			for (i = size; i <= __MAX_BYTES; i += __ALIGN) {
				my_free_list = free_list + FREELIST_INDEX(i);
				p = *my_free_list;
				if (0 != p) { // free list  内尚有未用区块。
					//  调整  free list 以释出未用区块
					*my_free_list = p -> free_list_link;
					start_free = (char *)p;
					end_free = start_free + i;
					// 递归呼叫自己,为了修正 nobjs。
					return( chunk_alloc (size, nobjs));
					// 注意,任何残余零头终将被编入适当的  free-list 中备用。
				}
			}
			end_free = 0;  // 如果出现意外(山穷水尽,到处都没内存可用了)
			// 呼叫第一级配置器,看看 out-of-memory 机制能否尽点力
			start_free = (char *)malloc_alloc::allocate(bytes_to_get);
			// 这会导致掷出异常(exception),或内存不足的情况获得改善
			}
		heap_size += bytes_to_get;
			end_free = start_free + bytes_to_get;
		//  递归呼叫自己,为了修正 nobjs。
			return(chunk_alloc(size, nobjs));
		}
	}
	
	
	
	
	//传回一个大小为 n的对象,并且有时候会为适当的 freelist 增加节点.
	//假设 n已经适当上调至 8的倍数。
	template <bool threads, int inst>
		void* __default_alloc_template<threads, inst>::refill(size_t n)
	{
		int nobjs = 20;
		// 呼叫 chunk_alloc() ,尝试取得 nobjs个区块做为  free list 的新节点。
		// 注意参数 nobjs是 pass by reference 。
		char * chunk = chunk_alloc(n, nobjs);
		obj ** 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);
		
		// 以下在 chunk空间内建立 freelist
		result = (obj *)chunk;//这一块准备传回给客端
		// 以下导引  free list 指向新配置的空间(取自记忆池)
		*my_free_list = next_obj = (obj *)(chunk + n);
		// 以下将  free list  的各节点串接起来。
		for (i = 1; ; i++)  //从 1 开始,因为第 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);
	}
	
	template <bool threads, int inst>
		void*
		__default_alloc_template<threads, inst>::reallocate(void *p,
		size_t old_sz,
		size_t new_sz)
	{
		void * result;
		size_t copy_sz;
		
		if (old_sz > (size_t) __MAX_BYTES && new_sz > (size_t) __MAX_BYTES) 
		{
			return(realloc(p, new_sz));
		}
		if (ROUND_UP(old_sz) == ROUND_UP(new_sz))
			return(p);
		result = allocate(new_sz);
		copy_sz = new_sz > old_sz? old_sz : new_sz;
		memcpy(result, p, copy_sz);
		deallocate(p, old_sz);
		return(result);
	}
	
	
	template <bool threads, int inst>
		void __default_alloc_template<threads, inst>::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;
	}
	
	/////////////////////////////////////////////////////////////////
	
#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:
	static T * allocate(size_t n)
	{
		return 0==n ? 0 : (T*)Alloc::allocate(n * sizeof(T));
	}
	static T * allocate(void)
	{
		return (T*)Alloc::allocate(sizeof(T));
	}
	static void deallocate(T *p, size_t n)
	{
		if(0==n)
			Alloc::deallocate(p, n*sizeof(T));
	}
	static void deallocate(T *p)
	{
		Alloc::deallocate(p,sizeof(T));
	}
	};

第二级配置器多了一些机制,避免太多小额区块造成内存的破碎。小额区块带来的其实不仅是内存破碎而已,配置时的额外负担(overhead)也是一大问题 8 。额外负担永远无法避免,毕竟系统要靠这多出来的空间来管理内存,
但是区块愈小,额外负担所占的比例就愈大、愈显得浪费。
SGI第二级配置器的作法是,如果区块够大,超过 128 bytes,就移交第一级配置器处理。当区块小于 128 bytes,则以记忆池(memory pool)管理,此法又称为次层配置(sub-allocation):每次配置一大块内存,并维护对应之自由串行(free-
list)。下次若再有相同大小的内存需求,就直接从free-lists中拨出。如果客端释还小额区块,就由配置器回收到free-lists中—是的,别忘了,配置器除了负责配置,也负责回收。为了方便管理,SGI第二级配置器会主动将任何小额区块的内存需求量上调至8的倍数(例如客端要求 30 bytes,就自动调整为 32bytes),并维护 16 个 free-lists,各自管理大小分别为 8, 16, 24, 32, 40, 48, 56, 64, 72,80, 88, 96, 104, 112, 120, 128 bytes的小额区块。free-lists 的节点结构如下:
union obj {
union  obj * free_list_link;
char client_data[1]; /* The client sees this. */
};
可能我们会想为了维护串行(lists),每个节点需要额外的指标(指向下一个节点),这不又造成另一种额外负担吗?你的顾虑是对的,但早已有好的解决办法。注意,上述 obj 所用的是 union ,由于 union 之故,从其第一字段观之, obj 可被视为一个指标,指向相同形式的另一个 obj 。从其第二字段观之, obj 可被视为一个指标,指向实际区块,如图2-4。一物二用的结果是,不会为了维护串行所必须的指针而造成内存的另一种浪费(我们正在努力樽节内存的开销呢)。
这种技巧在强型(strongly typed)语言如 Java 中行不通,但是在非强型语言如 C++中十分普遍 。free-list 这是一个小额区块这些区块已交给客端使用,所以 free-list不再指向它们。
 

上述的  chunk_alloc() 函式以 end_free - start_free  来判断记忆池的水量。如果水量充足,就直接拨出 20 个区块传回给 free list。如果水量不足以提供 20 个区块,但还足够供应一个以上的区块,就拨出这不足20个区块的空间出去。这时
候其pass by reference 的  nobjs  参数将被修改为实际能够供应的区块数。如果记忆池连一个区块空间都无法供应,对客端显然无法交待,此时便需利用 malloc()从 heap 中配置内存,为记忆池注入活水源头以应付需求。新水量的大小为需求
量的两倍,再加上一个随着配置次数增加而愈来愈大的附加量。
举个例子,假设程序一开始,客端就呼叫 chunk_alloc(32,20) ,于是malloc() 配置 40个 32bytes区块,其中第 1 个交出,另 19 个交给 free_list[3]维 护 , 余20 个 留 给 记 忆 池 。 接 下 来 客 端 呼 叫 chunk_alloc(64,20) , 此 时free_list[7]  空空如也,必须向记忆池要求支持。记忆池只够供应 (32*20)/64=10个 64bytes区块,就把这 10 个区块传回,第 1 个交给客端,余 9个由  free_list[7]维护。此时记忆池全空。接下来再呼叫 chunk_alloc(96, 20) ,此时  free_list[11]空空如也,必须向记忆池要求支持,而记忆池此时也是空的,于是以 malloc() 配置 40+n(附加量)个 96bytes 区块,其中第 1 个交出,另 19 个交给  free_list[11]维护,余 20+n(附加量)个区块留给记忆池……。万一山穷水尽,整个system heap 空间都不够了(以至无法为记忆池注入活水源头), malloc() 行动失败, chunk_alloc() 就㆕处寻找有无「尚有未用区块,且区块够大」之free lists。找到的话就挖一块交出,找不到的话就呼叫第一级配置器。第一级配置器其实也是使用 malloc() 来配置内存,但它有 out-of-memory处理机制(类似 new-handler 机制),或许有机会释放其它的内存拿来此处使用。如果可以,就成功,否则发出bad_alloc异常。
以上便是整个第二级空间配置器的设计。

补充内容:

容器:序列式容器,关联式容器

序列式容器(Sequence Contiainers):array(build-in),vector,heap,priority-queue,list,slist,deque,stack,queue

关联式容器(Associative Containers):RB-tree,set,map,multiset,multimap,hashtablke,hash_set,hash_map,hash_multiset,hash_multimap

一. malloc calloc realloc _alloca free ; new detele

  1. 静态申请在堆区,Test t; 动态申请在栈区,ps:     Test *pt = malloc(sizeof(Test)); pt->fun(); 空类大小是1 ; //在申请空间时候调用了构造  

  2. c语言中maclloc在调用时要强转而且计算开辟大小,而且在程序结束时要进行手动 释放,而且在释放前要析构对象 。 c++则用new detele 代替了。new申请空间,构造对象;deletel析构对象,释放空间。

  3. new,operator new, new operator , placement new

            new 和detele 可以进行重载,

operator new  原型为  void * operator new(sizt_t *) //无符号整型

placement new调用格式: new(p)int(10); //在原有的空间上构造一个对象进行赋值,默认构造在第一个空间上,定位可以进行控制需要重载new

void* operator new(sizt_t sz , int *ptr,int pos)

{

return &ptr[pos];  //进行第几个空间进行赋值 ps: new(p)[

}

allocator.construct(&_Acc:_Value(_S),_X) // 具有通用性

//深拷贝,浅拷贝 默认情况按成员赋值

二.stl中的空间配置器

SGI_STL

#include<stl_alloc.h>

1.向堆区申请 2.多线程 3.内存不足时候的应变 4.小型区块造成的内存碎片问题

一级空间配置器是 128k以内 

   set_new_handler c++才有;c 没有

 思考: 2^32 /4 申请不了空间 再处于2  可以申请 ???

针对申请内存空间,set_new_handler()是一个回调函数, // 回调函数 , 预防内存申请不成功 这是一个死循环函数 ,内部存在while(1).

参考文献:《STL源码剖析》

猜你喜欢

转载自blog.csdn.net/zzb2019/article/details/81145436