首页 > 代码库 > 图解STL内存管理的两种边界情况(STL源码剖析补充)

图解STL内存管理的两种边界情况(STL源码剖析补充)

图解STL内存管理的两种边界情况(STL源码剖析补充)


第一种情况就是内存池剩余的小字节空间怎么处理,会不会有内存泄露,答案肯定是不会,但是这个过程是怎么处理的,以下的代码已经简化处理,直接放到VS2010里就可以运行


#include<stdio.h>
#include<stdlib.h>


static const size_t __ALIGN=8;
static const size_t __MAX_BYTES=128;
static const size_t __NFREELISTS=__MAX_BYTES/__ALIGN;



class __default_alloc_template
{
private:
	//讲bytes上调至8的倍数
	static size_t ROUND_UP(size_t bytes)
	{		
		//高效率版本,没有用到乘法器,当比8多1时,就应该加倍9+7=16,多2时9+8=17,然后与1000000变成16,为8的整倍数
		return (((bytes)+__ALIGN-1)&~(__ALIGN-1));
	}      

	//free_list节点构造
public:
	union obj
	{
		union obj* free_list_link;
		char client_data[1];
	};

private:
	static obj *volatile free_list[__NFREELISTS];

	//根据块大小,选择使用第n号的free_list,n从0开始
	static size_t FREELIST_INDEX(size_t bytes)
	{
		return (((bytes)+__ALIGN-1)/__ALIGN-1);
	}

public:
	static void *refill(size_t n);

	static char *chunk_alloc(size_t size,int &nobjs);

public:
	//内存分配状态
	static char *start_free;
	static char *end_free;
	static size_t heap_size;
	static size_t nmalloc;

public:
	static void *allocate(size_t n)
	{
		obj *volatile *my_free_list;
		obj *result;

		//大于128调用第一级配置器,这里忽略大内存的处理

		//在16个链表里面找到合适的free_list
		my_free_list=free_list+FREELIST_INDEX(n);
		result=*my_free_list;
		if(result==0)
		{
			void *r=refill(ROUND_UP(n));
			return r;
		}
		//调整free_list,返回分配的空间,自由链表指向余下的空间首地址..会用到字节对齐?
		*my_free_list=result->free_list_link;
		printf("把%d字节的空间给容器使用\n",n);
		return result;		
	}

	static void *deallocate(void *p,size_t n)
	{
	}

	static void *reallocate(void *p,size_t old_sz,size_t new_sz)
	{
	}
};          


char *__default_alloc_template::start_free=0;

char *__default_alloc_template::end_free=0;

size_t __default_alloc_template::heap_size=0;

size_t __default_alloc_template::nmalloc=2;

__default_alloc_template::obj *volatile
	__default_alloc_template::free_list[__NFREELISTS]=
{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0};



void *__default_alloc_template::refill(size_t n)
{
	int nobjs=20;

	char *chunk=chunk_alloc(n,nobjs);


	obj *volatile *my_free_list;
	obj *result;
	obj *current_obj,*next_obj;
	int i;

	if(1==nobjs) //chunk_alloc函数里修改了nobjs
		return (chunk);

	my_free_list=free_list+FREELIST_INDEX(n);
	result=(obj*)chunk;
	
	*my_free_list=next_obj=(obj*)(chunk+n);

	//以下将free list的各个节点串联起来
	for(i=1;;i++)
	{
		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;
	}

}




char *__default_alloc_template::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;		
		printf("内存池拨出%d个%d字节空间,总共%d字节给%d字节自由链表,内存池剩下%d字节空间\n",nobjs,size,nobjs*size,size,end_free-start_free);
		return (result);
	}
	else if (bytes_left>=size)
	{
		nobjs=bytes_left/size;
		total_bytes=size*nobjs;
		result=start_free;
		start_free+=total_bytes;
		printf("内存池拨出%d个%d字节空间,总共%d字节给%d字节自由链表,内存池剩下%d字节空间\n",nobjs,size,nobjs*size,size,end_free-start_free);
		return result;
	}
	else
	{
		//一个块大小的内存都没有了
		//把内存池接到另外一块的头,不分配不使用任何空间
		size_t bytes_to_get=2*total_bytes+ROUND_UP(heap_size>>4);//?
		
		if(bytes_left>0)
		{
			//内存池还有一些零头,把他接到另外一个链表的头那里
			obj *volatile *my_free_list=free_list+FREELIST_INDEX(bytes_left);
			printf("内存池拨出%d字节给自由链表%d\n",bytes_left,FREELIST_INDEX(bytes_left));
			((obj*)start_free)->free_list_link=*my_free_list;
			*my_free_list=(obj*)start_free;
		}

		if(nmalloc>0)
		{
			start_free=(char*)malloc(bytes_to_get);
			printf("申请了%d字节的空间\n",bytes_to_get);
			nmalloc--;
		}
		else
			start_free=0;


		if(0==start_free)
		{
			//heap空间不足
			int i;
			obj *volatile *my_free_list,*p;
			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;
					//递归调用自己,修正nbojs
					return chunk_alloc(size,nobjs);
				}
			}
			end_free=0;
			printf("out of memory\n");

		}

		heap_size+=bytes_to_get;
		end_free=start_free+bytes_to_get;
		//递归调用自己,为了修正nobjs
		return chunk_alloc(size,nobjs);

	}
}

typedef __default_alloc_template alloc;

//allocator包装类
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));
	}
	//以下先省略...
};


void main()
{
	simple_alloc<int,alloc> data_allocator;
	data_allocator.allocate(1);
	data_allocator.allocate(16);
	data_allocator.allocate(32);
}

结果



过程分析


1.分配208字节的空间给自由链表,剩下160个字节留给内存池


2.要分配64字节的块,160字节只够分配2个空间,将剩下32个字节


3.要分配128字节的块,还有32字节怎么办,先把32字节分配给自由链表,但未使用


4.再分配20*128字节的空间给自由链表#15,剩下2560+heap_size>>4空间给内存池





第二部分是内存耗尽的特殊情况,为了模拟内存耗尽,我们需要稍微修改一下代码,设置成申请一次之后就不能再申请内存了


#include<stdio.h>
#include<stdlib.h>


#define DEBUG_OUT_OF_MEMORY

static const size_t __ALIGN=8;
static const size_t __MAX_BYTES=128;
static const size_t __NFREELISTS=__MAX_BYTES/__ALIGN;



class __default_alloc_template
{
private:
	//讲bytes上调至8的倍数
	static size_t ROUND_UP(size_t bytes)
	{		
		//高效率版本,没有用到乘法器,当比8多1时,就应该加倍9+7=16,多2时9+8=17,然后与1000000变成16,为8的整倍数
		return (((bytes)+__ALIGN-1)&~(__ALIGN-1));
	}      

	//free_list节点构造
public:
	union obj
	{
		union obj* free_list_link;
		char client_data[1];
	};

private:
	static obj *volatile free_list[__NFREELISTS];

	//根据块大小,选择使用第n号的free_list,n从0开始
	static size_t FREELIST_INDEX(size_t bytes)
	{
		return (((bytes)+__ALIGN-1)/__ALIGN-1);
	}

public:
	static void *refill(size_t n);

	static char *chunk_alloc(size_t size,int &nobjs);

public:
	//内存分配状态
	static char *start_free;
	static char *end_free;
	static size_t heap_size;

#ifdef DEBUG_OUT_OF_MEMORY
	static size_t nmalloc;
#endif

public:
	static void *allocate(size_t n)
	{
		obj *volatile *my_free_list;
		obj *result;

		//大于128调用第一级配置器,这里忽略大内存的处理

		//在16个链表里面找到合适的free_list
		my_free_list=free_list+FREELIST_INDEX(n);
		result=*my_free_list;
		if(result==0)
		{
			void *r=refill(ROUND_UP(n));
			return r;
		}
		//调整free_list,返回分配的空间,自由链表指向余下的空间首地址..会用到字节对齐?
		*my_free_list=result->free_list_link;
		printf("用掉自由链表%d的%d个字节的空间\n",FREELIST_INDEX(n)+1,n);
		return result;
	}

	static void *deallocate(void *p,size_t n)
	{
	}

	static void *reallocate(void *p,size_t old_sz,size_t new_sz)
	{
	}
};          


char *__default_alloc_template::start_free=0;

char *__default_alloc_template::end_free=0;

size_t __default_alloc_template::heap_size=0;

#ifdef DEBUG_OUT_OF_MEMORY
size_t __default_alloc_template::nmalloc=1;
#endif

__default_alloc_template::obj *volatile
	__default_alloc_template::free_list[__NFREELISTS]=
{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0};



void *__default_alloc_template::refill(size_t n)
{
	int nobjs=20;

	char *chunk=chunk_alloc(n,nobjs);


	obj *volatile *my_free_list;
	obj *result;
	obj *current_obj,*next_obj;
	int i;

	if(1==nobjs) //chunk_alloc函数里修改了nobjs
		return (chunk);

	my_free_list=free_list+FREELIST_INDEX(n);
	result=(obj*)chunk;
	
	*my_free_list=next_obj=(obj*)(chunk+n);

	//以下将free list的各个节点串联起来
	for(i=1;;i++)
	{
		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;
	}

}




char *__default_alloc_template::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;		
		printf("内存池拨出%d个%d字节空间,总共%d字节给%d字节自由链表,内存池剩下%d字节空间\n",nobjs,size,nobjs*size,size,end_free-start_free);
		return (result);
	}
	else if (bytes_left>=size)
	{
		nobjs=bytes_left/size;
		total_bytes=size*nobjs;
		result=start_free;
		start_free+=total_bytes;
		printf("内存池拨出%d个%d字节空间,总共%d字节给%d字节自由链表,内存池剩下%d字节空间\n",nobjs,size,nobjs*size,size,end_free-start_free);
		return result;
	}
	else
	{
		//一个块大小的内存都没有了
		//把内存池接到另外一块的头,不分配不使用任何空间
		size_t bytes_to_get=2*total_bytes+ROUND_UP(heap_size>>4);//?
		
		if(bytes_left>0)
		{
			//内存池还有一些零头,把他接到另外一个链表的头那里
			obj *volatile *my_free_list=free_list+FREELIST_INDEX(bytes_left);
			//剩余88字节讲移给第十一个自由链表
			printf("内存池拨出%d字节给自由链表%d\n",bytes_left,FREELIST_INDEX(bytes_left)+1);
			((obj*)start_free)->free_list_link=*my_free_list;
			*my_free_list=(obj*)start_free;
		}


#ifdef DEBUG_OUT_OF_MEMORY
		if(nmalloc>0)
		{
#endif
			start_free=(char*)malloc(bytes_to_get);
			printf("申请了%d字节的空间\n",bytes_to_get);
			nmalloc--;

#ifdef DEBUG_OUT_OF_MEMORY
		}
		else
			start_free=0;
#endif



		if(0==start_free)
		{
			//heap空间不足
			int i;
			obj *volatile *my_free_list,*p;
			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;
					printf("从自由链表%d挤出未用空间到内存池\n",i);
					start_free=(char*)p;
					end_free=start_free+i;
					//递归调用自己,修正nbojs
					return chunk_alloc(size,nobjs);
				}
			}
			end_free=0;
			printf("out of memory\n");
			getchar();

		}

		heap_size+=bytes_to_get;
		end_free=start_free+bytes_to_get;
		//递归调用自己,为了修正nobjs
		return chunk_alloc(size,nobjs);

	}
}

typedef __default_alloc_template alloc;

//allocator包装类
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));
	}
	//以下先省略...
};


void main()
{
	simple_alloc<int,alloc> data_allocator;
	data_allocator.allocate(2);
	data_allocator.allocate(8);
	data_allocator.allocate(2);
	data_allocator.allocate(2);
	data_allocator.allocate(2);
	data_allocator.allocate(2);
	data_allocator.allocate(2);
	data_allocator.allocate(2);
	data_allocator.allocate(2);
	data_allocator.allocate(2);
	data_allocator.allocate(2);
	data_allocator.allocate(2);
	data_allocator.allocate(2);
	data_allocator.allocate(2);
	data_allocator.allocate(2);
	data_allocator.allocate(2);
	data_allocator.allocate(2);
	data_allocator.allocate(2);
	data_allocator.allocate(2);
	data_allocator.allocate(2);
	data_allocator.allocate(2);
	data_allocator.allocate(2);
	data_allocator.allocate(2);
	data_allocator.allocate(2);
	data_allocator.allocate(2);
	data_allocator.allocate(2);
}

结果




1、先分配20*8字节空间给自由链表#0,使用1个块,剩下19个块未使用。再用剩下的160字节给自由链表#3,使用1个块,剩下4个块未使用



2.把自由链表#0指向的内存使用完,模拟内存不足,此时内存已没有空间



3.这时要再分配8字节的空间,要递增查询自由链表是否还有可用空间,还给内存池



4.把挤出来的那部分内存给自由链表#0,使用1个块,剩下3个块



图解STL内存管理的两种边界情况(STL源码剖析补充)