图解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.分配20个8字节的空间给自由链表,剩下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个块

时间: 2024-10-05 08:31:53

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

[LeetCode][14]Longest Common Prefix解析 两种算法和底层源码的深入对比-Java实现

Q: Write a function to find the longest common prefix string amongst an array of strings. A: 这题的大概意思就是说给你一组字符串找出其中最长的哪个通用的前缀出来.这个东西不难找,但是如何找的又快又好不简单.其实这题本来就是easy题,但是却让我联想到了<数据结构与算法分析>上的一道题目,那道题目是这样的: 给一个8900个字的字典,从中间找出类似abc.bbc.abb这样单词中只有一个字母不同的单词进行

STL源码剖析——STL函数对象

前言 在STL中,函数对象也是比较重要的,有时候可以限定STL算法的行为,例如在前面介绍的<STL算法剖析>中,每个算法基本上都提供了两个操作版本,其中就用一个版本允许用户指定函数对象,这样可以根据用户的需要对算法进行操作.函数对象是一种具有函数特质的对象,所以可以作为算法的参数.本文介绍的函数对象比较简单,是基于一元或者二元操作结构的算术类函数对象.关系运算类函数对象.逻辑运算类函数对象.在定义函数对象时,为了使其具有函数行为,则必须重载operator()操作符.本文源码出自SGI STL

STL源码剖析——算法之set集合算法

前言 本节介绍set集合的相关算法,分别是并集set_union,差集set_difference,交集set_intersection 和对称差集set_symmetric_difference,这是个函数都提供了两个版本的函数原型:第一个版本是采用默认的排序比较方式operator<:第二个版本是用户通过仿函数comp自行指定排序方式.注意:这四个算法接受的输入区间都是有序的,输出也是有序的.下面对set算法进行剖析,具体注释详见源码,同时给出例子说明该算法的功能.本文源码摘自SGI STL

STL内存管理

过年在家无事看了<STL源码剖析>,开学了将看过的东西总结一下,以防忘记. 先从STL的内存管理开始总结.掌管STL内存控制的是一个叫空间适配器(alloc)的东西.STL有两个空间适配器,SGI标准空间适配器(allocate)和SGI特殊的空间适配器(alloc),前者只是对c++的new和delete进行了简单的封装.下面主要介绍的是alloc空间适配器. 一般而言,c++的内存配置和释放操作是这样的: class Foo {...} Foo* pf = new Foo: delete

go---&gt;共享内存和通信两种并发模式原理探究

共享内存和通信两种并发模式原理探究 并发理解 人类发明计算机编程的本质目的是为了什么呢?毫无疑问是为了解决人类社会中的各种负责业务场景问题.ok,有了这个出发点,那么想象一下,比如你既可以一心一意只做一件事,你也可以同时做多件事,比如,你计划今天上午计划就是看足球比赛,ok,你今天的工作就是串行的,单进程的,你只需要完成一件事.但是不巧呢,你妈妈说让你帮她切肉,你妈妈上午要出门有点事,同时不巧呢,你老婆说她上午也要出门,让你帮着打扫家里卫生,这时你今天就要同时做三件事,看比赛,切肉,打扫卫生.这

windows 内存管理的几种方式及其优缺点

windows 内存管理方式主要分为:页式管理,段式管理,段页式管理. 页式管理的基本原理是将各进程的虚拟空间划分为若干个长度相等的页:页式管理把内存空间按照页的大小划分成片或者页面,然后把页式虚拟地址与内存地址建立一一对应的页表:并用相应的硬件地址变换机构来解决离散地址变换问题.页式管理采用请求调页或预调页技术来实现内外存存储器的统一管理.其优点是没有外碎片,每个内碎片不超过页的大小.缺点是,程序全部装入内存,要求有相应的硬件支持.例如地址变换机构缺页中断的产生和选择淘汰页面等都要求有相应的硬

【SSH进阶之路】一步步重构容器实现Spring框架——解决容器对组件的“侵入式”管理的两种方案--主动查找和控制反转(九)

目录 [SSH进阶之路]一步步重构容器实现Spring框架--从一个简单的容器开始(八) [SSH进阶之路]一步步重构容器实现Spring框架--解决容器对组件的"侵入式"管理的两种方案--主动查找和控制反转(九) [SSH进阶之路]一步步重构容器实现Spring框架--配置文件+反射实现IoC容器(十)(未更新) [SSH进阶之路]一步步重构容器实现Spring框架--彻底封装,实现简单灵活的Spring框架(十一)(未更新) 对于IOC的原理,我们曾经写过一篇博文,[SSH进阶之路

《python源码剖析》笔记 pythonm内存管理机制

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.内存管理架构 Python的内存管理机制都有两套实现:debug模式和release模式 Python内存管理机制的层次结构: 图16-1 第0层是操作系统提供的内存管理接口,如malloc.free 第1层是Python基于第0层操作系统的内存管理接口包装而成的,主要是为了处理与平台相关的内存分配行为. 实现是一组以PyMem_为前缀的函数族 两套接口:函数和宏. 宏,可以避免函数调

移动站Web开发图片自适应两种常见情况解决方案

本文主要说的是Web中图片根据手机屏幕大小自适应居中显示,图片自适应两种常见情况解决方案.开始吧 在做配合手机客户端的Web wap页面时,发现文章对图片显示的需求有两种特别重要的情况,一是对于图集,这种文章只需要左右滑动浏览,最好的体验是让图片缩放显示在屏幕有效范围内,防止图片太大导致用户需要滑动手指移动图片来查看这种费力气的事情,用户体验大大降低.二是图文混排的文章,图片最大宽度不超过屏幕宽度,高度可以auto.这两种情况在项目中很常见.另外,有人说做个图片切割工具,把图片尺寸比例都设定为统