以下是阿狸对c++中空间适配器的学习总结吗,希望可以帮助到大家。
什么是空间是适配器?
顾名思义就是为了高效的管理空间,进行空间的申请与回收。其在我们使用c++时候,一直在默默的工作。虽然我们在日常工作中用不到,但是了解它的原理,就像了解一件事情的背后,对我们理解c++有更好的帮助。
接下来就让我为大家来揭秘!
为什么需要空间适配器呢?
我们在c++中使用vector,map等容器的时候,所需要的空间都是需要通过new来申请的这样代码虽然可以运行,但是会有以下不足之处。
1:空间的申请与释放需要用户自己管理,容易造成内存泄漏
2:频繁向系统申请小块内存快,容易造成内存碎片
3:频繁向系统申请小块内存,影响程序运行效率
4:直接使用malloc和new进行申请,会对空间进行浪费
5:空间申请失败怎么办
6:代码结构混乱,代码服用率低
7:存在线程安全问题
因此,我们需要设计一块高效的内存管理机制!即SGL-STL空间适配器
总结上面我们遇到的问题,主要还是向系统频繁的申请小块内存所导致的;那么多大的内存算是小块内存呢?在SGL-STL中以128作为小块内存和大块内存的分界线,将空间配置器分为两级。
如下所示:
ok,接下来让我们一起看看一二级空间适配器的区别
首先让我们看看一级空间配置器
一级空间适配器的原理是非常简单的,其直接对malloc和free进行了封装。因为简单,所以让我们一起来欣赏这段优美的代码。
//一级空间适配器
template <int inst>
class __malloc_alloc_template
{
private:
static void *oom_malloc(size_t);
public:
// 对malloc的封装
static void * allocate(size_t n) {
// 申请空间成功,直接返回,失败交由oom_malloc处理
void *result = malloc(n);
if (0 == result)
result = oom_malloc(n);
return result;
}
// 对free的封装
static void deallocate(void *p, size_t /* n */){
free(p);
}
// 模拟set_new_handle
// 该函数的参数为函数指针,返回值类型也为函数指针
// void (* set_malloc_handler( void (*f)() ) )()
static void(*set_malloc_handler(void(*f)()))(){
void(*old)() = __malloc_alloc_oom_handler;
__malloc_alloc_oom_handler = f;
return(old);
}
};
// malloc申请空间失败时代用该函数
template <int inst>
void * __malloc_alloc_template<inst>::oom_malloc(size_t n) {
void(*my_malloc_handler)();
void *result;
for (;;)
{
// 检测用户是否设置空间不足应对措施,如果没有设置,抛异常,模式new的方式
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);
}
}
typedef __malloc_alloc_template<0> malloc_alloc;
总结老来说,一级空间适配器,就是对malloc和free的封装,只是在其上增加的set_new_handle(空间不足时的自定义操作)的思想。
接下来就让我们看看二级空间适配器
二级空间适配器专门负责小于128byte的小内存块。
那么其实怎样提升小块内存的申请与释放的呢?
首先SGI-STL采用了内存池的技术来提高申请空间的速度以及减少空间的浪费,采用哈希桶的方式来提高用户获得空间的速度&高效管理。
内存池:先申请一块较大的内存块做备用,当需要内存时,直接到内存池中去取,当内存池中空间不够时,再向内存中去取,当用户不用时,直接还给内存池即可。这样避免了向系统申请小块内存而造成效率低,内存碎片以及额外浪费的问题。
往往内存池会采用链表的进行管理。
但是SGI-STL中的二级空间配置器并没有采用链表的方式管理内存池,而是采用了哈希桶的方式进行了管理。
如下图所示:
根据上图我们可看出,二级空间适配器中的内存池以8byte为单位进行了对齐。
那么,为什么以8为单位对齐呢?这是因为要考虑指针,这样32位下的4字节指针和64位的8字节指针都可以在内存池中放下了。
ok,接下来就让我们看看二级空间适配器的流程。
看完流程,接下来让我们欣赏下这段优美的代码
前期准备的代码;
// 去掉代码中繁琐的部分
template <int inst>
class __default_alloc_template
{
private:
enum { __ALIGN = 8 }; // 如果用户所需内存不是8的整数倍,向上对齐到8的整数倍
enum { __MAX_BYTES = 128 }; // 大小内存块的分界线
enum { __NFREELISTS = __MAX_BYTES / __ALIGN }; // 采用哈希桶保存小块内存时所需桶的个数
// 如果用户所需内存块不是8的整数倍,向上对齐到8的整数倍
static size_t ROUND_UP(size_t bytes)
{
return (((bytes)+__ALIGN - 1) & ~(__ALIGN - 1));
}
private:
// 用联合体来维护链表结构,这样可以使整体占用一块内存,而结构体可能会占用多个地方的内存
union obj
{
union obj * free_list_link;
char client_data[1]; /* The client sees this. */
};
private:
static obj * free_list[__NFREELISTS];
// 哈希函数,根据用户提供字节数找到对应的桶号
static size_t FREELIST_INDEX(size_t bytes)
{
return (((bytes)+__ALIGN - 1) / __ALIGN - 1);
}
// start_free与end_free用来标记内存池中大块内存的起始与末尾位置
static char *start_free;
static char *end_free;
// 用来记录该空间配置器已经想系统索要了多少的内存块
static size_t heap_size;
// ...
}
申请内存的代码;
// 函数功能:向空间配置器索要空间
// 参数n: 用户所需空间字节数
// 返回值:返回空间的首地址
static void * allocate(size_t n) {
obj * __VOLATILE * my_free_list;
obj * __RESTRICT result;
// 检测用户所需空间释放超过128(即是否为小块内存)
if (n > (size_t)__MAX_BYTES)
{
// 不是小块内存交由一级空间配置器处理
return (malloc_alloc::allocate(n));
}
// 根据用户所需字节找到对应的桶号
my_free_list = free_list + FREELIST_INDEX(n);
result = *my_free_list;
// 如果该桶中没有内存块时,向该桶中补充空间
if (result == 0)
{
// 将n向上对齐到8的整数被,保证向桶中补充内存块时,内存块一定是8的整数倍
void *r = refill(ROUND_UP(n));
return r;
}
// 维护桶中剩余内存块的链式关系
*my_free_list = result->free_list_link;
return (result);
};
在上面的流程图中,我们看到还有一个内存填充的过程;
具体的填充流程如下
填充实例代码如下:
// 函数功能:向哈希桶中补充空间
// 参数n:小块内存字节数
// 返回值:首个小块内存的首地址
template <int inst>
void* __default_alloc_template<inst>::refill(size_t n) {
// 一次性向内存池索要20个n字节的小块内存
int nobjs = 20;
char * chunk = chunk_alloc(n, nobjs);
obj ** my_free_list;
obj *result;
obj *current_obj, *next_obj;
int i;
// 如果只要了一块,直接返回给用户使用
if (1 == nobjs)
return(chunk);
// 找到对应的桶号
my_free_list = free_list + FREELIST_INDEX(n);
// 将第一块返回值用户,其他块连接在对应的桶中
// 注:此处代码逻辑比较简单,但标准库实现稍微有点复杂
result = (obj *)chunk;
*my_free_list = next_obj = (obj *)(chunk + n);
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;
}
}
return(result);
}
当然,内存不是想申请就可以得到的,那如果没有足够的内存时会怎样?
请看下面的流程图
期中后颜色越深,表示内存申请走的步骤越多。
好了,讲到这里,1,2级的内存配置器已经讲完了;接下来让我们再看看空间配置器中的空间回收
首先请看流程图!
回收实例代码
// 函数功能:用户将空间归还给空间配置器
// 参数:p空间首地址 n空间总大小
static void deallocate(void *p, size_t n) {
obj *q = (obj *)p;
obj ** my_free_list;
// 如果空间不是小块内存,交给一级空间配置器回收
if (n > (size_t)__MAX_BYTES)
{
malloc_alloc::deallocate(p, n);
return;
}
// 找到对应的哈希桶,将内存挂在哈希桶中
my_free_list = free_list + FREELIST_INDEX(n);
q->free_list_link = *my_free_list;
*my_free_list = q;
}
好了,以上就是本次介绍的全部内容了。