上一篇我们介绍了STL对象的构造与析构,这篇介绍STL内存的配置与释放。
STL有两级空间配置器,默认是使用第二级。第二级空间配置器会在某些情况下去调用第一级空间配置器。空间配置器都是在allocate函数内分配内存,在deallocate函数内释放内存。
 
第一级空间配置器
 
第一级配置器只是对malloc函数和free函数的简单封装,在allocate内调用malloc,在deallocate内调用free。同时第一级配置器的oom_malloc函数,用来处理malloc失败的情况。如下所示:
allocate对malloc函数简单封装 :
static void *allocate(size_t n)
{
void *result = malloc(n);
if (NULL == result)
result = oom_malloc(n);
return result;
}

deallocate对free函数简单封装 :

static void deallocate(void *p, size_t) { free(p); }

oom_malloc调用外部提供的malloc失败处理函数,然后重新试着再次调用malloc。重复执行此过程,直到malloc成功为止 :

template <int inst>
void* __malloc_alloc<inst>::oom_malloc(size_t n)
{
void (*my_malloc_handler)();
void *result;
for (;;)
{
my_malloc_handler = malloc_alloc_oom_handler;
if (NULL == my_malloc_handler)
__THROW_BAD_ALLOC;
(*my_malloc_handler)();
result = malloc(n);
if (result)
return result;
}
}

第二级空间配置器

第一级配置器直接调用malloc和free带来了几个问题:
1.内存分配/释放的效率低。2. 当配置大量的小内存块时,会导致内存碎片比较严重。3. 配置内存时,需要额外的部分空间存储内存块信息,所以配置大量的小内存块时,还会导致额外内存负担。
 
第二级配置器维护了一个自由链表数组,每次需要分配内存时,直接从相应的链表上取出一个内存节点就完成工作,效率很高。
 
自由链表数组
自由链表数组其实就是个指针数组,数组中的每个指针元素指向一个链表的起始节点。数组大小为16,即维护了16个链表,链表的每个节点就是实际的内存块,相同链表上的内存块大小都相同,不同链表的内存块大小不同,从8一直到128。如下所示,obj为链表上的节点,free_list就是链表数组。
//自由链表
union obj
{
union obj *free_list_link;
char data[];
};
//自由链表数组
static obj *volatile free_list[__NFREELISTS];
内存分配
allocate函数内先判断要分配的内存大小,若大于128字节,直接调用第一级配置器,否则根据要分配的内存大小从16个链表中选出一个链表,取出该链表的第一个节点。若相应的链表为空,则调用refill函数填充该链表。如下:
template <bool threads>
void *__default_alloc<threads>::allocate(size_t n)
{
obj *volatile *my_free_list;
obj *result;
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 == NULL)
{
//第n号链表无内存块,则准备重新填充该链表
void *r = refill(ROUND_UP(n));
return r;
}
*my_free_list = result->free_list_link;
return result;
}
填充链表
若allocate函数内要取出节点的链表为空,则会调用refill函数填充该链表。
refill函数内会先调用chunk_alloc函数从内存池分配一大块内存,该内存大小默认为20个链表节点大小,当内存池的内存也不足时,返回的内存块节点数目会不足20个。接着refill的工作就是将这一大块内存分成20份相同大小的内存块,并将各内存块连接起来形成一个链表。如下:
template <bool threads>
void *__default_alloc<threads>::refill(size_t n)
{
int nobjs = __NOBJS;
char *chunk = chunk_alloc(n, nobjs); //从内存池获取内存
if (nobjs == ) //只能分配一块,则直接返回给调用者
return chunk;
obj *volatile *my_free_list;
obj *result, *next_obj, *current_obj;
result = (obj *)chunk;
my_free_list = free_list + FREELIST_INDEX(n);
*my_free_list = next_obj = (obj *)(chunk + n);
for (int i = ; i < nobjs - ; i++) //将剩下的区块添加进链表
{
current_obj = next_obj;
next_obj = (obj *)(char *)(next_obj + n);
current_obj->free_list_link = next_obj;
}
//最后一块
current_obj = next_obj;
current_obj->free_list_link = NULL;
return result;
}
内存池
chunk_alloc函数内管理了一块内存池,当refill函数要填充链表时,就会调用chunk_alloc函数,从内存池取出相应的内存。
在chunk_alloc函数内首先判断内存池大小是否足够填充一个有20个节点的链表,若内存池足够大,则直接返回20个内存节点大小的内存块给refill。如下:
        if (size_left >= total_size)  //内存池剩余空间满足需求
{
result = start_free;
start_free += total_size;
return result;
}

若内存池大小无法满足20个内存节点的大小,但至少满足1个内存节点,则直接返回相应的内存节点大小的内存块给refill;

        else if (size_left >= size)  //剩余空间不能全部满足,但至少满足一块
{
nobjs = size_left / size;
result = start_free;
start_free += nobjs * size;
return result;

若内存池连1个内存节点大小的内存块都无法提供,则chunk_alloc函数会将内存池中那一点点的内存大小分配给其他合适的链表,然后去调用malloc函数分配的内存大小为所需的两倍。若malloc成功,则返回相应的内存大小给refill;若malloc失败,会先搜寻其他链表的可用的内存块,添加到内存池,然后递归调用chunk_alloc函数来分配内存,若其他链表也无内存块可用,则只能调用第一级空间配置器,因为第一级空间配置器有malloc失败的出错处理函数,最终的希望只能寄托在那里了。

如下是整个chunk_alloc函数:
template <bool threads>
char *__default_alloc<threads>::chunk_alloc(size_t size, int& nobjs)
{
size_t total_size = size * nobjs;
char *result;
size_t size_left = end_free - start_free;
if (size_left >= total_size) //内存池剩余空间满足需求
{
result = start_free;
start_free += total_size;
return result;
}
else if (size_left >= size) //剩余空间不能全部满足,但至少满足一块
{
nobjs = size_left / size;
result = start_free;
start_free += nobjs * size;
return result;
}
else //连一个区块都无法满足
{
if (size_left > ) //将残余内存分配给其他合适的链表
{
obj *volatile *my_free_list = free_list + FREELIST_INDEX(size_left);
((obj *)start_free)->free_list_link = *my_free_list; //在头部插入
*my_free_list = (obj *)start_free;
}
size_t bytes_to_get = * total_size + ROUND_UP(heap_size >> );
start_free = (char *)malloc(bytes_to_get);
if (start_free == NULL) //堆空间不足
{
int i;
obj *volatile *my_free_list;
obj *p;
for (i = size; i < __MAX_BYTES; i++)
{
my_free_list = free_list + FREELIST_INDEX(i);
p = *my_free_list;
if (p != NULL)
{
*my_free_list = p->free_list_link;
start_free = (char *)p;
end_free = start_free + i;
return chunk_alloc(size, nobjs);
}
}
end_free = NULL;
//调用第一级配置器
start_free = (char *)malloc_alloc::allocate(bytes_to_get);
}
heap_size += bytes_to_get;
end_free = start_free + heap_size;
return chunk_alloc(size, nobjs);
}
}
内存释放
第二级配置器的deallocate函数并不会直接释放内存块。当内存块大小大于128字节时才会直接释放,否则会将内存块回收到相应的链表当中。如下:
void __default_alloc<threads>::deallocate(void *p, size_t n)
{
//大于__MAX_BYTES,则释放该内存
if (n > (size_t)__MAX_BYTES)
malloc_alloc::deallocate(p, n);
obj *q = (obj *)p;
obj *volatile *my_free_list;
my_free_list = free_list + FREELIST_INDEX(n);
//小于__MAX_BYTES,则回收区块,并未释放
q->free_list_link = *my_free_list;
*my_free_list = q;
}

内存对外接口

STL对外提供了一个simple_alloc类,该类提供统一的接口:allocate函数、deallocate函数,使得外部无需关心使用的是几级内存配置器。另外simple_alloc类将外部所需的对象个数转换为字节。如下。

template <typename T, typename Alloc>
class simple_alloc
{
public:
static T *allocate(size_t n) // 个数
{
return n == ? : (T*)Alloc::allocate(n * sizeof(T)); // 将个数转换为字节
} static T *allocate(void)
{
return (T*)Alloc::allocate(sizeof(T));
} static void deallocate(T *p, size_t n) // 个数
{
if (n != )
Alloc::deallocate(p, n * sizeof(T));
} static void deallocate(T *p)
{
Alloc::deallocate(p, sizeof(T));
}
};

(全文完)

附:
一款简易版STL的实现,项目地址:https://github.com/zinx2016/MiniSTL/tree/master/MiniSTL
 
 
 
 

STL—内存的配置与释放的更多相关文章

  1. STL——空间的配置和释放std::alloc(第一级配置器和第二级配置器)

    1 空间的配置和释放,std::alloc 对象构造前的空间配置和对象析构后的空间释放,由<stl_alloc.h>负责,SGI对此的设计哲学如下: 向system heap要求空间 考虑 ...

  2. [STL]双层级配置器

    考虑到过多“小型区块”可能造成的内存碎片问题,SGI设计了双层级配置器: 第一级配置器直接调用malloc()和free(): 第二级配置器分两种情况:当配置区块大于128字节时,调用第一级配置器:当 ...

  3. 带你深入理解STL之空间配置器(思维导图+源码)

    前不久把STL细看了一遍,由于看得太"认真",忘了做笔记,归纳和总结这步漏掉了.于是为了加深印象,打算重看一遍,并记录下来里面的一些实现细节.方便以后能较好的复习它. 以前在项目中 ...

  4. SGI STL内存管理

    前言 万丈高楼平地起,内存管理在C++领域里扮演着举足轻重的作用.对于SGI STL这么重量级的作品,当然少不了内存管理的实现.同时,想要从深层次理解SGI STL的原理,必须先将内存管理这部分的内容 ...

  5. 空间的配置和释放 std::alloc

    看完了对象的构造行为和内存释放前的对象的析构行为,我们现在来看看内存的配置和释放. 对象构造前的空间分配和析构后的空间释放,定义在头文件<stl_alloc.h>中.其设计思想是: 向sy ...

  6. STL内存配置器

    一.STL内存配置器的总体设计结构 1.两级内存配置器:SGI-STL中设计了两级的内存配置器,主要用于不同大小的内存分配需求,当需要分配的内存大小大于128bytes时, 使用第一级配置器,否则使用 ...

  7. SGI STL内存配置器存在内存泄漏吗?

    阅读了SGI的源码后对STL很是膜拜,很高质量的源码,从中学到了很多.温故而知新!下文中所有STL如无特殊说明均指SGI版本实现. STL 内存配置器 STL对内存管理最核心部分我觉得是其将C++对象 ...

  8. C++STL内存配置的设计思想与关键源码分析

    说明:我认为要读懂STL中allocator部分的源码,并汲取它的思想,至少以下几点知识你要了解:operator new和operator delete.handler函数以及一点模板知识.否则,下 ...

  9. STL 内存释放

    C++ STL 中的map,vector等内存释放问题是一个很令开发者头痛的问题,关于 stl内部的内存是自己内部实现的allocator,关于其内部的内存管理本文不做介绍,只是 介绍一下STL内存释 ...

随机推荐

  1. Xmpp学习之Smack发送消息JID变乱码

    Xmpp学习之Smack发送消息JID变乱码 版权声明:本文为博主原创文章,未经博主允许不得转载. 转载请表明出处:http://www.cnblogs.com/cavalier-/p/6947723 ...

  2. Python+requests库 POST接口图片上传

    捕获到POST接口的请求参数,如下: 包括data.file两个部分,上传代码如下: 比较简单,直接发送files参数即可 heads中 content-type 折腾了1个小时,呵呵

  3. SQL之删除触发器

    比如要删除的触发器名字叫dbo.test_trigger. 先判断这个触发器是否存在,判断存在后删除 if exists (select * from sysobjects where name = ...

  4. Java NIO学习笔记六 SocketChannel 和 ServerSocketChannel

    Java NIO SocketChannel Java NIO SocketChannel是连接到TCP网络socket(套接字)的通道.Java NIO相当于Java Networking的sock ...

  5. win7安装oracle10g数据库 提示“…实际版本为6.1”

    1.最重要的是需要以管理员的身份操作 2.修改重要的文件 然后解压,在目录中找到refhost.xml(有两个,我的一个是在stage\prereq\db目录下,一个是在stage\prereq\db ...

  6. 取消PHPCMS V9后台新版本升级提示信息

    方法非常简单,只要找到文件: phpcms/libs/classes/update.class.php 文件,修改第50行的代码(大概位置): function notice() { return $ ...

  7. AVAudioFoundation(2):音视频播放

    本文转自:AVAudioFoundation(2):音视频播放 | www.samirchen.com 本文主要内容来自 AVFoundation Programming Guide. 要播放 AVA ...

  8. 探索Windows命令行系列(3):命令行脚本基础

    1.实用功能 1.1.为脚本添加注释 1.2.控制命令的回显 1.3.使用数学表达式 1.4.向脚本传递参数 2.使用变量 2.1.变量的命名及定义 2.2.调用变量 2.3.变量的作用域 3.结构语 ...

  9. sqlserver 复制表结构(可以含有数据 或 只要表结构)

    sqlserver 复制表结构(可以含有数据 或 只要表结构) SELECT * INTO bb FROM aa(NOLOCK) WHERE 1=0

  10. Linux下pecl命令无法执行的解决

    pecl install swoole 提示如下错误:bash: pecl: command not found执行yum install php-pear生效