图解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个块