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 ...
随机推荐
- AC日记——[HEOI2012]旅行问题 bzoj 2746
2746 思路: 建立ac自动机,然后把fail树抽出来: 然后在fail树上走lca(神奇): 代码: #include <cstdio> #include <vector> ...
- 洛谷 P1182 数列分段`Section II`【二分答案】
[代码]: #include<bits/stdc++.h> const double eps = 1e-8; const int maxn = 1e6+5; #define inf 0x3 ...
- 8、Django实战第8天:session和cookie自动登录机制
因为http是无状态协议,因此,并不会记录用户的登录状态.在早期,是直接把用户名和密码等信息存储在浏览器的cookie来实现记录用户密码登录. 但是这样存在安全隐患,只要别人登录你的电脑cookie信 ...
- 加密连接工具Cryptcat
加密连接工具Cryptcat Cryptcat是网络工具Netcat的加密版本.Cryptcat支持TCP.UDP两种网络协议.它可以在两个计算机之间建立指定的连接,并使用特定的密钥对传输数据进行 ...
- [Atcoder Grand Contest 002] Tutorial
Link: AGC002 传送门 A: …… #include <bits/stdc++.h> using namespace std; int a,b; int main() { sca ...
- 使用Maven下载jar包
有些开源项目不直接提供jar包的下载,而是建议使用Maven下载,以开源库hipster(https://github.com/citiususc/hipster,http://www.hipster ...
- MORMOT数据库连接池
MORMOT数据库连接池 MORMOT封装了一堆的PROPS控件,用于连接各种数据库. MORMOT的封装是武装到了牙齿的,这堆PROPS控件居然数据库连接池也封装好了.这就为我们省了不少事,笔者非常 ...
- 【java】处理时间字段 在数据库查询的时候只想要年月日,不想要时分秒 ,java中设置时间类型为年月日,java中设置Date中的时分秒为00.00.000
JDK8 中最简单的处理方式: @Test public void dateTest(){ Date now = new Date(); System.out.println(now); // jav ...
- Web服务器在外网能裸奔多久?
很多时候我们轻易地把Web服务器暴露在公网上,查看一下访问日志,可以看到会收到大量的攻击请求,这个是网站开通后几个小时收到的请求: 1. 探测服务器信息 在上线一分钟,收到OPTION请求探测. ...
- ubuntu ufw防火墙简易教程(转)
ufw是一个主机端的iptables类防火墙配置工具,比较容易上手.一般桌面应用使用ufw已经可以满足要求了. 安装方法 sudo apt-get install ufw 当然,这是有图形界面的(比较 ...