STL源码剖析(空间配置器)
前言
在STL中,容器的定义中都带一个模板参数,如vector
template <class T, class Alloc = alloc>
class vector {...}
其中第二个参数就是该容器使用的空间配置器,其中缺省使用STL已经实现的空间配置器(alloc),
该配置器使用malloc/free等为vector分配内存。
缺省的空间配置器
alloc定义了两级的空间配置器,第一级是对malloc/free简单的封装。
而为了解决内存碎片的问题,跟进行内存管理,alloc实现的第二级的空间配置器。
第二级空间配置器在分配大块内存(大于128bytes)时,会直接调用第一级空间配置器,
而分配小于128bytes的内存时,则使用内存池跟free_list进行内存分配/管理。
第一级空间配置器
基本实现如下(跟SGI STL可能有点出入,主要是提取核心的内容)
class base_alloc {
public:
// 只是对malloc/free的简单封装
static void* allocate(size_t n)
{
void* res = malloc(n);
if ( == res) res = oom_malloc(n);
return res;
}
static void* reallocate(void* p, size_t new_sz)
{
void* res = realloc(p, new_sz);
if ( == res) res = oom_realloc(p, new_sz);
return res;
}
static void deallocate(void* p)
{
free(p);
}
// 用来设置内存不足时的处理函数 该函数参数跟返回值都是一个函数指针
// 一般会抛出异常/尝试回收内存
static void(*set_handler(void(*f)()))()
{
void(*old)() = _oom_handler;
_oom_handler = f;
return old;
}
private:
// 用来处理内存不足的情况
static void* oom_malloc(size_t n)
{
void(*my_handler)();
void* res;
for (;;)
{
my_handler = _oom_handler;
if ( == my_handler) { return NULL; }
(*my_handler)();
if (res = malloc(n)) return res;
}
}
// 用来处理内存不足的情况
static void* oom_realloc(void* p, size_t n)
{
void(*my_handler)();
void* res;
for (;;)
{
my_handler = _oom_handler;
if ( == my_handler) { return NULL; }
(*my_handler)();
if (res = reallocate(p, n)) return res;
}
}
// 由用户设置,在内存不足的时候进行处理,由上面两个函数调用
static void(*_oom_handler)();
};
// 处理函数默认为0
void(*base_alloc::_oom_handler)() = ;
它可以设定一个处理内存不足的时候的处理函数(跟set_new_handler类似)。
第二级空间配置器
该配置器维护一个free_list,这是一个指针数组。
在分配内存的时候,补足8bytes的倍数,free_list数组中每个指针分别管理分配大小为8、16、24、32...bytes的内存。
下图表示从二级空间配置器中分配内存时是如何维护free_list的(建议参考下面源码阅读)。
开始所有指针都为0,没有可分配的区块时(就是free_list[i]==0)会从内存池中分配内存(默认分配20个区块)插入到free_list[i]中。
然后改变free_list[i]的指向,指向下一个区块(free_list_link指向下一个区块,如果没有则为0)。

下图表示二级空间配置器回收内存时是如何维护free_list结构的。
回收的时候只是将区块插入到free_list[i]的开头,这块内存用于下次分配的时候使用。

该配置器实现如下(同样提取核心的部分):
enum { _ALIGN = }; // 对齐
enum { _MAX_BYTES = }; // 区块大小上限
enum { _NFREELISTS = _MAX_BYTES / _ALIGN }; // free-list个数
class default_alloc {
private:
// 将bytes上调到8的倍数
static size_t ROUND_UP(size_t bytes)
{
return (bytes + _ALIGN - ) & ~(_ALIGN - );
}
private:
union obj {
union obj* free_list_link;
char client_data[];
};
private:
// 16个free-lists 各自管理分别为8,16,24...的小额区块
static obj* free_list[_NFREELISTS];
// 根据区块大小,决定使用第n号free-list
static size_t FREELIST_INDEX(size_t bytes)
{
return (bytes + _ALIGN - ) / _ALIGN - ;
}
// 分配内存,返回一个大小为n的区块,可能将大小为n的其他区块加入到free_list
static void* refill(size_t n)
{
// 默认分配20个区块
int nobjs = ;
char* chunk = chunk_alloc(n, nobjs);
obj** my_free_list;
obj* result, *current_obj, *next_obj;
// 如果只分配了一个区块,直接返回
if ( == nobjs) return chunk;
// 否则将其他区块插入到free list
my_free_list = free_list + FREELIST_INDEX(n);
result = (obj*)chunk;
// 第一个区块返回 后面的区块插入到free list
*my_free_list = next_obj = (obj*)(chunk + n);
for (int i = ;; ++i)
{
current_obj = next_obj;
next_obj = (obj*)((char*)next_obj + n);
// 最后一个next的free_list_link为0
if (nobjs - == i)
{
current_obj->free_list_link = ;
break;
}
current_obj->free_list_link = next_obj;
}
return result;
}
// 分配内存
// 在内存池容量足够时,只调整start_free跟end_free指针
// 在内存池容量不足时,调用malloc分配内存(2 * size * nobjs + ROUND_UP(heap_size >> 4),每次调整heap_size += 本次分配内存的大小)
static char* chunk_alloc(size_t size, int& nobjs);
static char* start_free; //内存池的起始位置
static char* end_free; //内存池的结束位置
static size_t heap_size; //分配内存时的附加量
public:
static void* allocate(size_t n)
{
obj** my_free_list;
obj* result;
// 大于128就调用第一级空间配置器
if (n > (size_t)_MAX_BYTES)
return base_alloc::allocate(n);
// 寻找适当的free-list
my_free_list = free_list + FREELIST_INDEX(n);
result = *my_free_list;
// 没有可用的free list
if (result == )
return refill(ROUND_UP(n));
// 调整free list 移除free list
*my_free_list = result->free_list_link;
return result;
}
static void deallocate(void* p, size_t n)
{
obj* q = (obj*)p;
obj** my_free_list;
// 大于128就调用第一级空间配置器
if (n > (size_t)_MAX_BYTES)
{
base_alloc::deallocate(p);
return;
}
// 寻找对应的free list
my_free_list = free_list + FREELIST_INDEX(n);
// 调整free list 回收区块(将区块插入到my_free_list)
q->free_list_link = *my_free_list;
*my_free_list = q;
}
static void reallocate(void* p, size_t old_sz, size_t new_sz);
};
char* default_alloc::start_free = ;
char* default_alloc::end_free = ;
size_t default_alloc::heap_size = ;
default_alloc::obj* default_alloc::free_list[_NFREELISTS] = { , , , , , , , , , , , , , , , };
STL源码剖析(空间配置器)的更多相关文章
- STL源码剖析 — 空间配置器(allocator)
前言 以STL的实现角度而言,第一个需要介绍的就是空间配置器,因为整个STL的操作对象都存放在容器之中. 你完全可以实现一个直接向硬件存取空间的allocator. 下面介绍的是SGI STL提供的配 ...
- STL源码剖析——空间配置器Allocator#2 一/二级空间配置器
上节学习了内存配置后的对象构造行为和内存释放前的对象析构行为,在这一节来学习内存的配置与释放. C++的内存配置基本操作是::operator new(),而释放基本操作是::operator del ...
- STL源码剖析——空间配置器Allocator#1 构造与析构
以STL的运用角度而言,空间配置器是最不需要介绍的东西,因为它扮演的是幕后的角色,隐藏在一切容器的背后默默工作.但以STL的实现角度而言,最应该首先介绍的就是空间配置器,因为这是这是容器展开一切运作的 ...
- STL源码剖析——空间配置器Allocator#3 自由链表与内存池
上节在学习第二级配置器时了解了第二级配置器通过内存池与自由链表来处理小区块内存的申请.但只是对其概念进行点到为止的认识,并未深入探究.这节就来学习一下自由链表的填充和内存池的内存分配机制. refil ...
- STL源码剖析:配置器
作用:对内存的管理 接口:申请和释放 内容: 几个全局函数 一级配置器 二级配置器 准备知识 POD是什么: Plain Old Data简称POD,表示传统的C语言类型:与POD类型对应的是非POD ...
- 《STL源码剖析》相关面试题总结
原文链接:http://www.cnblogs.com/raichen/p/5817158.html 一.STL简介 STL提供六大组件,彼此可以组合套用: 容器容器就是各种数据结构,我就不多说,看看 ...
- 面试题总结(三)、《STL源码剖析》相关面试题总结
声明:本文主要探讨与STL实现相关的面试题,主要参考侯捷的<STL源码剖析>,每一个知识点讨论力求简洁,便于记忆,但讨论深度有限,如要深入研究可点击参考链接,希望对正在找工作的同学有点帮助 ...
- STL源码剖析之空间配置器
本文大致对STL中的空间配置器进行一个简单的讲解,由于只是一篇博客类型的文章,无法将源码表现到面面俱到,所以真正感兴趣的码农们可以从源码中或者<STL源码剖析>仔细了解一下. 1,为什么S ...
- 《STL源码剖析》环境配置
首先,去侯捷网站下载相关文档:http://jjhou.boolan.com/jjwbooks-tass.htm. 这本书采用的是Cygnus C++ 2.91 for windows.下载地址:ht ...
随机推荐
- hdu 1534(差分约束)
Schedule Problem Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) ...
- hdu5790
都快忘了在这类题的经典做法了…… 将字符串一个个的插入字典树,在字典树维护好有该前缀串s的最大编号字符串j,我们记作j控制了前缀串s 对于当前的第i个字符串,维护此时有当前每个字符串控制了多少个前缀串 ...
- 关于android studio3.0版本后引入资源问题error: failed linking file resources
最近更新阿里短视频的SDK,导入demo项目时,发现两个问题: 1.频繁报错AAPT2 error: file not found.以为直接Sync重新编译就可以,但是仍旧频繁提醒引入资源错误. 查找 ...
- ANDROID NFC读M1卡
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.Cons ...
- CodeVS 1226 倒水问题【DFS/BFS】
题目描述 Description 有两个无刻度标志的水壶,分别可装 x 升和 y 升 ( x,y 为整数且均不大于 100 )的水.设另有一水 缸,可用来向水壶灌水或接从水壶中倒出的水, 两水壶间,水 ...
- 大数模板(Java)
大数加法 /* 给出2个大整数A,B,计算A+B的结果. Input 第1行:大数A 第2行:大数B (A,B的长度 <= 10000 需注意:A B有可能为负数) Output 输出A + B ...
- 20、Flask实战第20天:Flask上下文
Local线程隔离对象 我们知道通过request可以获取表单中的数据.如果是多个用户同时在用网站,而全局request就只有一个,那么Flask是如何分辨哪用户对应哪个请求呢? 这种情况下,就会用到 ...
- Jenkins获取分支的插件
Jenkins--->xxxx--->配置--->参数化构建过程--->选择Git Parameter Plug-In插件 Name: git_branch Descripti ...
- 日期 function
SELECT SYSDATE, ADD_MONTHS(SYSDATE,), ADD_MONTHS(SYSDATE,), LAST_DAY(SYSDATE), MONTHS_BETWEEN(SYSDAT ...
- python 统计发送请求到接收response的时间
由于需要测试请求一个接口所耗用的时间,在网上查找资料也麻烦,所以自己总结一下 找到elapsed 函数 ,按照文档说的是获取请求发出的时间至响应到达经过的时间,,具体用法如下: 执行的结果是 微秒 单 ...