我是靠谱客的博主 飞快灰狼,最近开发中收集的这篇文章主要介绍深度解析--SGI STL 空间配置与释放1.SGI 空间配置与释放2. 两级配置器3. C++ new-handler机制4. free list的节点结构,觉得挺不错的,现在分享给大家,希望可以做个参考。
概述
欢迎大家来访二笙的小房子,一同学习分享生活!
文章目录
- 1.SGI 空间配置与释放
- 2. 两级配置器
- 2.1 一、二级配置器的关系
- 2.2 第一级配置器 __malloc_alloc_template
- 2.3 第二级配置器
- 2.3.1 空间配置函数alllocate()
- 2.3.2 空间释放函数 deallocate()
- 2.3.3 重新装填free_list
- 2.3.4 内存池取空间(chunk_alloc实现)
- 3. C++ new-handler机制
- 4. free list的节点结构
1.SGI 空间配置与释放
- 对象构造前的空间配置与对象析构后的空间释放,由<stl_alloc.h>负责,SGI主要考虑以下四个步骤:
1.向system heap要求空间
2.考虑多线程状态
3.考虑内存不足时的应对措施
4.考虑过多“小型区块”可能造成的内存碎片问题 - SGI以malloc()和free()完成内存的配置与释放,但考虑到小型区块可能造成的内存破碎问题,SGI设置了双层级配置器:
1.第一级配置器直接使用malloc()和free()
2.第二级配置器视情况采用不同的策略:
- 配置区块超过128bytes时,视之为足够大,便采用第一级配置器
- 配置区块小于128bytes时,视之为过小,采用
memory pool
整理方式
2. 两级配置器
- SGI封装了一个接口,名为simple_alloc(
使配置器的接口符合STL规格
),无论alloc被定义为第一级亦或第二级配置器,都采用该接口进行空间配置:
template <class T, class Alloc>
class simple_alloc {
public:
static T *allocate(size_ t n) //单纯的转调用函数,调用传递给配置器的成员函数
{return 0 == n? 0 : (T*) Alloc::allocate(n * szieof(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)); }
sattic void deallocate(T *p)
{ Alloc::deallocate(p, sizeof(T)); }
};
2.1 一、二级配置器的关系
- 在学习两级配置器之前首先通过下图了解一下一、二级配置器是如何实现的以及两者之间的关系:
2.2 第一级配置器 __malloc_alloc_template
- 主要以malloc()、free()、realloc()等C函数执行实际的内存配置、释放、重配置操作,并实现出C++ new-handler机制:
#include <iostream>
#define __throwBadAlloc cerr << "out of memory" << endl; exit(1);
template <int inst>
class __MallocAllocTemplate {
typedef void(*OOM_HANDLE)();
private:
//以下函数用来处理内存不足的情况,oom:out of memory
static void *oomMalloc(size_t);
static void *oomRealloc(void *, size_t);
static OOM_HANDLE OOM_Handle;
public:
static void * allocate(size_t n) {
void *result = malloc(n); //第一级配置器直接使用malloc
//如果内存不足则调用oomMalloc处理
if (0 == result) result = oomMalloc(n);
return result;
}
static void deallocate(void *p, size_t m) {
free(n); //第一级配置器直接调用free
}
static void * reallocate(void *p, size_t oldSz, size_t newSz) {
void *result = realloc(p, newSz); //第一级配置器直接调用realloc()
//内存不足调用oomRealloc
if (0 == result) result = oomRealloc(p, newSz);
return result;
}
//static void (* set_malloc_handle (void (*f)())) ()
//参数与返回值都是void (*)()
static OOM_HANDLE setMallocHandele(OOM_HANDLE f) {
OOM_HANDLE old = OOM_Handle;
OOM_Handle = f;
return old;
}
};
//设定初始指针为空
template <int inst>
void(*__MallocAllocTemplate<inst>::OOM_Handle)() = NULL;
//内存不足尝试再次申请
template <int inst >
void * __MallocAllocTemplate<inst>::oomMalloc(size_t n) {
void(*myMallocHandle)();
void *result;
for (;;) {
myMallocHandle = OOM_Handle; //不断尝试释放、配置、再释放、再配置...
if (0 == myMallocHandle) { __throwBadAlloc; }
(*myMallocHandle)(); //调用处理例程,企图释放内存
result = malloc(n); //再次尝试配置内存
if (result) return(result);
}
}
template <int inst >
void * __MallocAllocTemplate<inst>::oomRealloc(void *p, size_t n) {
void(*myMallocHandle)();
void *result;
for (;;) {
myMallocHandle = OOM_Handle; //不断尝试释放、配置、再释放、再配置...
if (0 == myMallocHandle) { __throwBadAlloc; }
(*myMallocHandle)(); //调用处理例程,企图释放内存
result = malloc(n); //再次尝试配置内存
if (result) return(result);
}
}
- 若内存不足处理函数oomMalloc与oomRealloc未设定
内存不足处理例程
,则直接丢出bad_alloc异常信息
2.3 第二级配置器
- 第二级配置器多了一些机制,避免小额区块造成内存的碎片,减少配置时的额外负担
- 具体实现:
1.如果区块够大,超过128bytes,移交第一级配置器处理
2.当区块小于128bytes,则以内存池管理:
- 每次配置一块内存,维护对应的自由链表;
- 下次收到相同大小的请求时,则直接从自由链表中拔出;
- 客端释还小额区块,就回收到free-lists中
- 第二级配置器主动将任何小额区块的内存需求上调至8的倍数(ROUND_UP函数实现)
- 以下是部分函数实现:
//提升函数,将bytes提升至8的倍数
static size_t ROUND_UP(size_t bytes) {
return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));
}
//16个free-lists
static obj * volatile free_list[__NFREELISTS];
//填充free_list
static size_t FREELIST_INDEX(size_t bytes) {
return (((bytes) + __ALIGN - 1)/__ALIGN - 1);
}
以下重点理解
2.3.1 空间配置函数alllocate()
- 步骤:
1.判断区块大小,大于128bytes调用第一级配置器
2.小于128bytes检查对应的free_list,如果有可用区块,直接拿来用;否则上调至8倍数边界,调用refill为free_list重新填充
static void * allocate(size_t n) {
obj * volatile * my_free_list;
obj * result;
if (n > (size_t) __MAX_BYTES) { //大于128调用第一级
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)); //没找到可用的free_list,重新填充free_list
return r;
}
//调整free_list
*my_free_list = result->free_list_link;
return (result);
};
2.3.2 空间释放函数 deallocate()
- 步骤:
1.判断区块大小,大于128bytes调用第一级配置器
2.小于128bytes则找出对应的free_list,将区块回收
static void deallocate(void *p, size_t n) {
obj *q = (obj *)p;
obj * volatile * my_free_list;
if (n > (size_t) __MAX_BYTES) { //大于128调用第一级配置器
malloc_alloc::deallocate(p, n);
return ;
}
my_free_list = free_list + FREELIST_INDEX(n);
q-> free_list_link = *my_free_list; //调整free_list,回收区块
*my_free_list = q;
}
2.3.3 重新装填free_list
- 步骤:
1.free list中无可用区块,调用refill(),为free list填充空间
2.新空间取自内存池(由chunk_alloc())完成,默认取20个新节点
3.如果只申请到一块,则分配给调用者;否则在chunk空间建立free list
template <bool threads, int inst>
void * __default_alloc_template<threads, inst>::refill(size_t n) {
int nobjs = 20; //默认取20块新节点
char * chunk = chunk_alloc(n, nobjs); //在内存池中申请
if (1 == nobjs) return(chunk); //只获得一块区块,给调用者使用
//否则跳整free list/,纳入新节点
obj * my_free_list = free_list + FREELIST_INDDEX(n);
obj * result = (obj*)chunk; //返回一块给客端
obj * next = (obj*)(chunk + n); //指向下一块内存
obj * cur = next;
*my_free_list = cur;
//在chunk空间建立free list连接
for (int i = 1; i < nobjs; ++i) {
next = (obj*)((char*)next + n);
cur->free_list_link = next;
cur = next;
}
cur->free_list_link = NULL;
return result;
}
2.3.4 内存池取空间(chunk_alloc实现)
- 步骤(主要分以下四个步骤进行):
1. 内存池剩余空间完全满足需求,则直接分配给用户
2. 若剩余空间不能完全满足,但足够提供一个以上的区块,则将这些区块分配给用户
3. 若剩余空间一个都满足不了,则将残余空间编入free list,然后配置heap空间:
1.向系统heap申请空间
2. 分配成功则返回
3. 分配失败则检索自己的free list查看是否有可用区块
4. 到处都无内存使用则调用第一级配置器
template <bool threads, int inst>
char *
__default_alloc_template<threads, inst>::
chunk_alloc(size_t size, int& nobjs) {
char *result;
size_t totalbytes = size * nobjs; //需求量
size_t bytesleft = end_free - start_free; //内存池剩余量
//如果剩余大于需求
if (bytesleft >= totalbytes) {
result = start_free;
start_free += totalbytes;
resturn result;
}
//剩余只能满足一个或以上的区块,不能全部满足
else if(bytesleft >= size) {
nobjs = bytesleft/size;
totalbytes = size * nobjs;
result = start_free;
start_free += totalbytes;
return result;
}
//剩余空间一个区块都不能满足
else {
//内存池中还有些许零头 ,将其分配给用户
if (bytesleft >0) {
//寻找适当的free list
obj * volatile *myfreelist = free_list + FREELIST_INDEX(bytesleft);
((obj *)start_free)->free_list_link = *myfreelist;
*myfreelist = (obj *)start_free;
}
size_t bytesToGet = 2 * totalbytes + ROUND_UP(heap_size>>4); //配置heap空间,初始量设为需求的两倍加上一个随配置次数增大的附加量
start_free = (char *)malloc(bytesToGet);
if (0 == start_free) {
//heap空间不足,malloc失败
int i;
obj * volatile * myfreelist, *p;
//搜寻free list,查找是否还有未用的区块
for (i = size; i <= _MAX_BYTES; i += _ALIGN) {
myfreelist = free_list + FREELIST_INDEX(i);
p = *myfreelist;
if (0 != p) {
//调整free list释放未用区块
myfreelist = p -> free_list_link;
start_free = (char*)p;
end_free = start_free + i;
return (chunk_alloc(size, nobjs)); //递归调用,修正nobjs
}
}
//所有地方都无内存可用,则调用第一级配置器,使用out of memory机制
end_free = 0;
start_free = (char *)malloc_alloc::allocate(bytesToGet);
}
//分配成功则返回
heap_size = bytesToGet;
end_free = start_free + bytesToGet;
return (chunk_alloc(size, nobjs)); //递归修正nobjs
}
}
3. C++ new-handler机制
本章在讲解第一级配置器时初次提及C++ new-handler机制,于是就去《Effective C++》进一步了解了有关new-handler机制,日后再次遇到时也能迅速回忆起。
- 1.“当operator new抛出异常以反映一个未获满足的内存需求之前,它会先调用一个客户指定的错误处理函数,一个所谓的new-handler”,这是Effective C++中的原话,用以引出new-handler,从这句话我们大概能知道new-handler是用来处理内存不足的一个机制
- 2.为了指定这个“用以处理内存不足”的函数,客户需要调用
set_new_handler
,定义于< new>的一个标准程序库函数:
namespace std {
typedef void (*new_handler)();
//返回值和参数都是new_handler
new_handler set_new_handler(new_handler p) throw();
}
- 3.set_new_handler的参数是一个指针,指向operator new无法分配足够内存时需要调用的函数:
void outOfMem()
{
std::cerr << "Unable to satisfy request for memoryn";
std::abort();
}
int main()
{
std::set_new_handler(outOfMem); //当内存不足时,调用处理函数outOfMem
int* pBigDataArrry = new int[100000000L];
...
}
- 4.设计一个良好的new-handler:
4.1 让更多内存可被使用:程序一开始执行就分配一大块内存,而后当new-handler第一次被调用,将它们释放还给程序
4.2 安装另一个new-handler:令new-handler修改一些会影响new-handler行为的数据
4.3 卸载new-handler将NULL指针传给set_new_handler
4.4 抛出bad_alloc的异常
4.5 不返回:调用abort或exit
- 5.基于以上设计,你可以定制你自己的new-handle机制
4. free list的节点结构
- 通过上图可以了解到STL通过指针来维护自由链表,有人可能会想每个节点都需要额外的指针,会不会造成额外的负担呢?答案是会的,那么STL的设计精妙之处在哪呢?我们可以先来看一看它的节点结构:
union obj {
union obj * free_list_link;
char client_data[1];
}
- "上述obj所用的union,由于union之故,从其第一字段观之,obj可被视为一个指针,指向相同形式的另一个obj,从其第二字段观之,obj可被视为一个指针,指向实际区块“,这段话前半部分很好理解,因为free_list_link本身就是一个obj的指针,但后半部分
obj可被视为一个指针,指向实际区块
,这一句话又怎么理解呢?
1.首先我们回顾下union的性质,一个union类任意时刻只允许其一个数据成员有值,且数据成员公用内存
2.由上,该free list节点结构union中的数据成员free_list_link和client_data公用一块内存,前者是指向下一节点的指针,后者是指向实际内存的指针,无论什么情况,二者只用其一,不分配的时候,节点就放在空闲链表之内,节点内容是指向下一节点的地址,如果被分配了,那么节点指向的就是实际的内存
最后
以上就是飞快灰狼为你收集整理的深度解析--SGI STL 空间配置与释放1.SGI 空间配置与释放2. 两级配置器3. C++ new-handler机制4. free list的节点结构的全部内容,希望文章能够帮你解决深度解析--SGI STL 空间配置与释放1.SGI 空间配置与释放2. 两级配置器3. C++ new-handler机制4. free list的节点结构所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复