STL源码剖析——空间配置器Allocator#2 一/二级空间配置器
上节学习了内存配置后的对象构造行为和内存释放前的对象析构行为,在这一节来学习内存的配置与释放。
C++的内存配置基本操作是::operator new(),而释放基本操作是::operator delete()。这两个全局函数相当于C的malloc() 和free() 函数。而SGI正是以malloc() 和free() 完成内存的配置与释放。
考虑到小型区块可能造成的内存破碎问题,SGI设计了两级的空间配置器。第一级直接使用malloc() 和free() ,而第二级则视情况采用不同的策略:当配置区块超过128bytes时,视为足够大,便调用第一级配置器;当配置区块小于128bytes时,视为过小,采用复杂的内存池(memery pool)分配方式,而不再求助于第一级配置器。我的理解是,容器在分配内存时,无论所需区块多大,它都是调用第二级配置器,而在第二级配置器内再做判断是否求助于第一级配置器。我们可以从这段代码中看到端倪,其中第一级配置器类为__malloc_alloc_template,第二级配置器类为 __default_alloc_template:
typedef __malloc_alloc_template<> malloc_alloc;
...
#ifdef __USE_MALLOC
...
typedef malloc_alloc alloc; // 令 alloc 為第一級配置器
...
#else
...
typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, > alloc; // 令 alloc 為第二級配置器
...
#endif
通过测试可知,SGI STL并未定义__USE_MALLOC,所以都是调用第二级配置器。
不过无论alloc被定义为第一级亦或是第二级配置器,SGI 还为它再包装一个接口如下,使配置器的接口能够符合STL规格:
template<class T, class 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)); }
};
其内部的四个成员函数其实都是单纯的转调用,调用泛型Alloc类的成员函数allocate亦或是deallocate来配置与释放内存。而Alloc一般在容器类的定义时就已经指派好了,例如在Vector类定义的开头:
template <class T, class Alloc = alloc> // 預設使用 alloc 為配置器
class vector {
...
protected:
// 專屬之空間配置器,每次配置一個元素大小
typedef simple_alloc<value_type, Alloc> data_allocator;
...
}
第一级配置器 __malloc_alloc_template
// 以下是第一級配置器。
// 注意,無「template 型別參數」。「非型別參數」inst 完全沒派上用場。
template <int inst>
class __malloc_alloc_template {
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 public:
static void * allocate(size_t n)
{
void *result = malloc(n); // 第一級配置器直接使用 malloc()
if ( == result) result = oom_malloc(n);
return result;
}
static void deallocate(void *p, size_t /* n */)
{
free(p); // 第一級配置器直接使用 free()
}
static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz)
{
void * result = realloc(p, new_sz); // 第一級配置器直接使用 realloc()
if ( == result) result = oom_realloc(p, new_sz);
return result;
}
// 以下類似 C++ 的 set_new_handler().
static void (* set_malloc_handler(void (*f)()))()
{
void (* old)() = __malloc_alloc_oom_handler;
__malloc_alloc_oom_handler = f;
return(old);
}
};
// malloc_alloc out-of-memory handling
#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
template <int inst>
void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = ;
#endif
template <int inst>
void * __malloc_alloc_template<inst>::oom_malloc(size_t n)
{
void (* my_malloc_handler)();
void *result;
for (;;) { // 不斷嘗試釋放、配置、再釋放、再配置…
my_malloc_handler = __malloc_alloc_oom_handler;
if ( == my_malloc_handler) { __THROW_BAD_ALLOC; }
(*my_malloc_handler)(); // 呼叫處理常式,企圖釋放記憶體。
result = malloc(n); // 再次嘗試配置記憶體。
if (result) return(result);
}
}
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 ( == my_malloc_handler) { __THROW_BAD_ALLOC; }
(*my_malloc_handler)(); // 呼叫處理常式,企圖釋放記憶體。
result = realloc(p, n); // 再次嘗試配置記憶體。
if (result) return(result);
}
}
可以看到,在正常情况会是使用malloc()来分配内存,使用free()释放内存,使用realloc()来重分配内存,并实现了类似C++ new-handler的机制,因为它并非使用::operator new来分配内存,所以不能直接运用C++ new-handler机制。而C++ new-handler机制,大概就是要求系统在内存配置需求无法被满足时,调用一个你所指定的函数。一旦::operator new无法完成任务,在丢出std::bad_alloc异常状态之前,会先调用由客端指定的处理例程。该例程通常就被称为new-handler。
理解这段源码的难点就在于处理内存不足的情况,在allocate()(realloc()同理)调用malloc()不成功后,会改调用oom_malloc()函数,该函数会尝试不断尝试调用__malloc_alloc_oom_handler,期待在某次调用后能够获得足够的内存而完成任务。但我们可以看到__malloc_alloc_oom_handler默认值是设为0的(41行),就是说如果用户没通过set_malloc_handler(31行)来为__malloc_alloc_oom_handler指定一个处理内存不足的函数,那么在oom_malloc()函数内便会直接调用__THROW_BAD_ALLOC(50行),丢出bad_alloc异常。这是我对上述源码的愚见,如有错误请指出。那么现在剩下的问题只有一个了,new-handler在解决内存不足的问题上有特定的模式,那么如何设计一个new-handler函数呢?这个问题留在日后解决......
第二级配置器 __default_alloc_template
第二级配置器多了一些机制,避免太多小额区块造成内存的碎片,且还会在配置时带来额外的负担,区块越小,额外负担所占的比例就越大:
SGI第二级配置器的做法是,如果区块够大,超过128bytes时,就移交给第一级配置器处理。否则,情况就有些复杂,需要一一道来:
首先先介绍第二级配置器用到一个内存管理结构——自由链表(free-list),在第二级配置器里会维护16个自由链表,每一个自由链表管理不同大小的区块,分别为8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128bytes,且第二级配置器会主动将任何小额区块的内存需求量上调至8的倍数(例如用户要求30bytes,就自动调整为32bytes,这决定了使用第几号自由链表)。
就一个单独的自由链表而言,其节点用一个union表示:
union obj {
union obj * free_list_link;
char client_data[]; /* The client sees this. */
};
里面的联合体指针就是指向下一个节点的指针,那个char是指向实际内存块的指针,采用联合体可以减少内存的消耗,不必专门维护一个指向下一个节点的指针。
当节点所指的内存块是空闲块时,obj被看做一个指针,指向下一个节点,当节点已经被分配了内存之后,被视为一个指针,指向实际区块(free_list_link自动失效)。
在一开始,自由链表数组全部被初始化为0,当用户需要某大小的区块(例如94bytes)时,寻找16个自由链表中适当的一个(则为11号),发现其11号自由链表为空,需要分配内存(一般分配40*96bytes大小的内存),在分配内存后会将第一块给用户,剩余一部分(19*96bytes)放进11号自由链表,另一部分(20*16bytes)留在内存池。如果是在自由链表不为空的情况下,例如当用户再需要一块94bytes大小的区块时,则直接把11号链表的表头取出,取出的表头的值(为free_list_link,指向链表第一块空内存块)便会成为用户申请地址返回给用户给用户,然后把表头的值指向的下一块空块作为新的表头。
enum {__ALIGN = }; // 小型區塊的上調邊界
enum {__MAX_BYTES = }; // 小型區塊的上限
enum {__NFREELISTS = __MAX_BYTES/__ALIGN}; // free-lists 個數
#endif
// 以下是第二級配置器。
// 注意,無「template 型別參數」,且第二參數完全沒派上用場。
template <bool threads, int inst>
class __default_alloc_template {
private:
//负责将用户需求的内存大小调整为8的倍数
static size_t ROUND_UP(size_t bytes) {
return (((bytes) + __ALIGN-) & ~(__ALIGN - ));
}
private:
union obj {
union obj * free_list_link;
char client_data[]; /* The client sees this. */
};
private:
# ifdef __SUNPRO_CC
static obj * __VOLATILE free_list[];
// Specifying a size results in duplicate def for 4.1
# else
static obj * __VOLATILE free_list[__NFREELISTS]; //自由链表
# endif
//以下函数根据区块大小,决定使用第n号free-list。n从0起:
static size_t FREELIST_INDEX(size_t bytes) {
return (((bytes) + __ALIGN-)/__ALIGN - );
} static void * allocate(size_t n)
{/*后述*/}
static void deallocate(void *p, size_t n)
{/*后述*/}
static void * reallocate(void *p, size_t old_sz, size_t new_sz); static void *refill(size_t n); //重新填充链表,下节详述 static char *chunk_alloc(size_t size, int &nobjs); //填充时用于分配内存,下节详述 // 内存池指针
static char *start_free;
static char *end_free;
static size_t heap_size; template <bool threads, int inst>char *__default_alloc_template<threads, inst>::start_free = ;
template <bool threads, int inst>char *__default_alloc_template<threads, inst>::end_free = ;
template <bool threads, int inst>size_t __default_alloc_template<threads, inst>::heap_size = ;
} template <bool threads, int inst>__default_alloc_template<threads, inst>::obj * __VOLATILE__
default_alloc_template<threads, inst> ::free_list[__NFREELISTS] = {, , , , , , , , , , , , , , , , };
空间配置函数allocate()
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));
}
//寻找16个自由链表中适当的那个表头
my_free_list = free_list + FREELIST_INDEX(n);
//表头的值作为该内存块的地址返回给用户
result = *my_free_list;
//倘若自由链表为空,准备分配内存填充链表
if (result == ) {
void *r = refill(ROUND_UP(n)); //将n调整为8的倍数再进行内存分配
return r;
}
//调整自由链表为表头的下一区块
*my_free_list = result -> free_list_link;
return (result);
};
空间释放函数deallocate():
static void deallocate(void *p, size_t n)
{
obj *q = (obj *)p; //将要归还的区块转换为联合体obj
obj * __VOLATILE * my_free_list;
if (n > (size_t) __MAX_BYTES) { //大于128bytes就调用第一级配置器
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;
}
STL源码剖析——空间配置器Allocator#2 一/二级空间配置器的更多相关文章
- STL源码剖析 — 空间配置器(allocator)
前言 以STL的实现角度而言,第一个需要介绍的就是空间配置器,因为整个STL的操作对象都存放在容器之中. 你完全可以实现一个直接向硬件存取空间的allocator. 下面介绍的是SGI STL提供的配 ...
- STL源码剖析之空间配置器
本文大致对STL中的空间配置器进行一个简单的讲解,由于只是一篇博客类型的文章,无法将源码表现到面面俱到,所以真正感兴趣的码农们可以从源码中或者<STL源码剖析>仔细了解一下. 1,为什么S ...
- 《STL源码剖析》环境配置
首先,去侯捷网站下载相关文档:http://jjhou.boolan.com/jjwbooks-tass.htm. 这本书采用的是Cygnus C++ 2.91 for windows.下载地址:ht ...
- (原创滴~)STL源码剖析读书总结1——GP和内存管理
读完侯捷先生的<STL源码剖析>,感觉真如他本人所说的"庖丁解牛,恢恢乎游刃有余",STL底层的实现一览无余,给人一种自己的C++水平又提升了一个level的幻觉,呵呵 ...
- 《STL源码剖析》相关面试题总结
原文链接:http://www.cnblogs.com/raichen/p/5817158.html 一.STL简介 STL提供六大组件,彼此可以组合套用: 容器容器就是各种数据结构,我就不多说,看看 ...
- STL源码剖析之序列式容器
最近由于找工作需要,准备深入学习一下STL源码,我看的是侯捷所著的<STL源码剖析>.之所以看这本书主要是由于我过去曾经接触过一些台湾人,我一直觉得台湾人非常不错(这里不涉及任何政治,仅限 ...
- 《STL源码剖析》读书笔记
转载:https://www.cnblogs.com/xiaoyi115/p/3721922.html 直接逼入正题. Standard Template Library简称STL.STL可分为容器( ...
- 面试题总结(三)、《STL源码剖析》相关面试题总结
声明:本文主要探讨与STL实现相关的面试题,主要参考侯捷的<STL源码剖析>,每一个知识点讨论力求简洁,便于记忆,但讨论深度有限,如要深入研究可点击参考链接,希望对正在找工作的同学有点帮助 ...
- 通读《STL源码剖析》之后的一点读书笔记
直接逼入正题. Standard Template Library简称STL.STL可分为容器(containers).迭代器(iterators).空间配置器(allocator).配接器(adap ...
- 【STL 源码剖析】浅谈 STL 迭代器与 traits 编程技法
大家好,我是小贺. 点赞再看,养成习惯 文章每周持续更新,可以微信搜索「herongwei」第一时间阅读和催更,本文 GitHub : https://github.com/rongweihe/Mor ...
随机推荐
- spaceclaim脚本(内摆线)
import math #导入数学模块,因为会使用π def x_comp(k,r,t): #定义x坐标的计算函数 return r * (k -1) * math.cos(t) + r * math ...
- ubuntu之路——day9.1 深度学习超参数的调优
参数重要性: 第一阶:α即learning rate 第二阶:momentum中的β,hidden units的数量,mini-batch的大小 第三阶:hidden layers的数量,learni ...
- DELPHI开发LINUX包
DELPHI开发LINUX包 我们知道,有了包的存在,开发插件架构的程序,才成为可能 . DELPHI在WINDOWS里面的包的扩展名是.bpl. 在LINUX里面的包的扩展名是.so. 怎样在LIN ...
- Django,Flask,Tornado三大框架对比,Python几种主流框架,13个Python web框架比较,2018年Python web五大主流框架
Django 与 Tornado 各自的优缺点Django优点: 大和全(重量级框架)自带orm,template,view 需要的功能也可以去找第三方的app注重高效开发全自动化的管理后台(只需要使 ...
- iTunes Connect上传APP屏幕快照图片失败 - 您必须上传有效的屏幕快照。
您必须上传有效的屏幕快照. 原因很简单:这个屏幕快照 要用 iPhone截屏才可以,你自已随便在电脑上截个图肯定不行 //--------------------------------------- ...
- 信息论 | information theory | 信息度量 | information measures | R代码(一)
这个时代已经是多学科相互渗透的时代,纯粹的传统学科在没落,新兴的交叉学科在不断兴起. life science neurosciences statistics computer science in ...
- 基础学习笔记之opencv(3):haartraining生成.xml文件过程[转]
1.准备正负样本: 在上一讲http://www.cnblogs.com/tornadomeet/archive/2012/03/27/2420088.html 中,我们已经收集到了训练所用的正样本. ...
- tomcat的AJP(定向包协议)协议
由于tomcat的html和图片解析功能相对其他服务器如apche等较弱,所以,一般都是集成起来使用,只有jsp和servlet服务交由tomcat处理,而tomcat和其他服务器的集成,就是通过aj ...
- postgre级联更新
常规写法 update t_table_copy a set content=( select content from t_table b where a.id = b.id ); 这些法,数据少了 ...
- bat批处理文件怎么将路径添加到path环境变量中
bat批处理文件怎么将路径添加到path环境变量中 摘自:https://zhidao.baidu.com/question/1887763143433391788.html 永久性的: @echo ...