今天看了STL中的空间配置器,写一篇博客小小的总结一下。
STL空间配置器的产生
在实际的软件开发中,当我们使用C++中的malloc、new、free和delete时,我们不可避免的会因为程序的需求,使用很多的小内存块,这个过程是不一定能够控制好的,例如下面的场景
而且一直调用malloc和new产生小块内存不光会产生内存碎片,而且系统调用会产生性能问题。
内碎片与外碎片
内碎片:因为内存对齐/访问效率(CPU取址次数)而产生 如 用户需要3字节,实际得到4或者8字节的问题,其中的碎片是浪费掉的。
外碎片:系统中内存总量足够,但是不连续,所以无法分配给用户使用而产生的浪费
于是为了解决内存碎片和系统性能等问题,STL的SGI空间配置器就因此而产生了,它一共有两层空间配置器,分别是第一级空间配置器和第二级空间配置器。第一级空间配置器被定义为__malloc_alloc_template
template <int inst> //inst 为预留参数,现在还没有什么具体作用
class __malloc_alloc_template
第二级空间配置器被定义为__default_alloc_template
template <bool threads, int inst> //threads用于多线程参数下,现在暂不讨论
class __default_alloc_template
今天我们主要来谈一下第一级空间配置器。
源码分析
oom—out of memory
处理内存不足的函数和函数指针
private:
static void *oom_malloc(size_t);
static void *oom_realloc(void *, size_t);
#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
static void (* __malloc_alloc_oom_handler)();
#endif
下面的函数负责开辟和释放空间
static void * allocate(size_t n)
{
//调用malloc开辟空间
void *result = malloc(n);
//如果开辟空间失败调用处理内存不足的函数
if (0 == result) result = oom_malloc(n);
//开辟空间成功则返回这块空间
return result;
}
static void deallocate(void *p, size_t /* n */)
{
//直接调用free()函数释放这块空间
free(p);
}
static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz)
{
//调用realloc()开辟空间
void * result = realloc(p, new_sz);
//开辟空间失败调用处理内存不足的函数
if (0 == result) result = oom_realloc(p, new_sz);
//开辟空间成功则返回这块空间
return result;
}
//这里模仿C++ operrator new 的set_new_handler机制
static void (* set_malloc_handler(void (*f)()))()
{
void (* old)() = __malloc_alloc_oom_handler;
__malloc_alloc_oom_handler = f;
return(old);
}
既然说到了这里,下面我们来谈一谈set_new_hanlder机制
参考《Effective C++》条款49。
在C++中我们用operator new来开辟一块空间:
当operator new申请一个内存失败的时候,它会进行如下的处理步骤:
1、如果存在客户指定的处理函数,则调用处理函数(new_handler),如果不存在则抛出一个异常。new_handler的模型为:void (*new_handler)()。
2、继续申请内存分配请求。
3、判断申请内存是否成功,如果成功则返回内存指针,如果失败转向处理步骤1
为了指定这个用以处理内存不足的函数,客户必须调用set_new_hanlder,它在标准模板库中是下面这么定义的:
namespace std
{
typedef void (*new_hanlder)();//newhanlder其实是一个函数指针
new_handler set_new_hanlder(new_hanlder p) throw();
//set_new_hanlder其实是一个不能够抛出异常的函数
}
下面我们来说一下怎么指定这个new_hanlder
class A
{
public:
static void OutOfMemory() //用户自己定义处理内存不足的函数
{
//...
}
static std::new_handler set_new_hanlder(std::new_handler p) throw();
//将函数名传给new_hanlder
};
当operator new无法满足内存申请时,它会不断调用new_handler函数就是用户自己定义的OutOfMemory(),直到系统能够找到足够的内存
现在我们知道了new操作失败后,系统地大概处理流程,以及怎么设置用户自定义处理函数,但是我们究竟可以在new_handler中做些什么处理呢?
1、删除其它无用的内存,使系统具有可以更多的内存可以使用,为下一步的内存申请作准备。
2、设置另外一个new_handler。如果当前的new_handler不能够做到更多的内存申请操作,或者它知道另外一个new_handler可以做到,则可以调用set_new_handler函数设置另外一个new_handler,这样在operator new下一次调用的时候,可以使用这个新的new_handler。
3、卸载new_handler,使operator new在下一次调用的时候,因为new_handler为空抛出内存申请异常。
4、抛出自定义异常
5、不再返回,调用abort或者exit退出程序
现在我们回归正轨看一下SGI空间配置器是如何处理内存不足的
//初始化hanlder
#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
template <int inst>
void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0;
#endif
template <int inst>
void * __malloc_alloc_template<inst>::oom_malloc(size_t n)
{
void (* my_malloc_handler)();
void *result;
for (;;) {
//这里是将my_malloc_hanlder函数指针指向用户定义的内存不足处理函数
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);
//如果内存空间分配不成功,则反复的执行内存不足处理函数
}
}
realloc的内存不足处理函数在这里也做了与上面的malloc内存不足处理函数一样的事情
template <int inst>
void * __malloc_alloc_template<inst>::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);
}
}
现在我们应该对SGI STL的一级空间配置器有所了解了,它用malloc()函数分配空间,如果分配成功直接返回这块空间,如果空间分配不成功并且用户自己定义了内存不足处理函数,就调用内存不足处理函数,然后在尝试去开辟空间,如果内存还是分配不成功就继续调用内存不足处理函数直到内存成功分配为止,如果用户没有定义内存不足处理函数,那么就会抛出一个异常。
这是SGI中的第一级空间配置器,下期我们还会分析第二级空间配置器,体会STL所存在的价值!