上节学习了内存配置后的对象构造行为和内存释放前的对象析构行为,在这一节来学习内存的配置与释放。

  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 一/二级空间配置器的更多相关文章

  1. STL源码剖析 — 空间配置器(allocator)

    前言 以STL的实现角度而言,第一个需要介绍的就是空间配置器,因为整个STL的操作对象都存放在容器之中. 你完全可以实现一个直接向硬件存取空间的allocator. 下面介绍的是SGI STL提供的配 ...

  2. STL源码剖析之空间配置器

    本文大致对STL中的空间配置器进行一个简单的讲解,由于只是一篇博客类型的文章,无法将源码表现到面面俱到,所以真正感兴趣的码农们可以从源码中或者<STL源码剖析>仔细了解一下. 1,为什么S ...

  3. 《STL源码剖析》环境配置

    首先,去侯捷网站下载相关文档:http://jjhou.boolan.com/jjwbooks-tass.htm. 这本书采用的是Cygnus C++ 2.91 for windows.下载地址:ht ...

  4. (原创滴~)STL源码剖析读书总结1——GP和内存管理

    读完侯捷先生的<STL源码剖析>,感觉真如他本人所说的"庖丁解牛,恢恢乎游刃有余",STL底层的实现一览无余,给人一种自己的C++水平又提升了一个level的幻觉,呵呵 ...

  5. 《STL源码剖析》相关面试题总结

    原文链接:http://www.cnblogs.com/raichen/p/5817158.html 一.STL简介 STL提供六大组件,彼此可以组合套用: 容器容器就是各种数据结构,我就不多说,看看 ...

  6. STL源码剖析之序列式容器

    最近由于找工作需要,准备深入学习一下STL源码,我看的是侯捷所著的<STL源码剖析>.之所以看这本书主要是由于我过去曾经接触过一些台湾人,我一直觉得台湾人非常不错(这里不涉及任何政治,仅限 ...

  7. 《STL源码剖析》读书笔记

    转载:https://www.cnblogs.com/xiaoyi115/p/3721922.html 直接逼入正题. Standard Template Library简称STL.STL可分为容器( ...

  8. 面试题总结(三)、《STL源码剖析》相关面试题总结

    声明:本文主要探讨与STL实现相关的面试题,主要参考侯捷的<STL源码剖析>,每一个知识点讨论力求简洁,便于记忆,但讨论深度有限,如要深入研究可点击参考链接,希望对正在找工作的同学有点帮助 ...

  9. 通读《STL源码剖析》之后的一点读书笔记

    直接逼入正题. Standard Template Library简称STL.STL可分为容器(containers).迭代器(iterators).空间配置器(allocator).配接器(adap ...

  10. 【STL 源码剖析】浅谈 STL 迭代器与 traits 编程技法

    大家好,我是小贺. 点赞再看,养成习惯 文章每周持续更新,可以微信搜索「herongwei」第一时间阅读和催更,本文 GitHub : https://github.com/rongweihe/Mor ...

随机推荐

  1. 膜态沸腾UDF【转载】

    膜态沸腾的UDF,添加注释.其中获取VOF梯度的方法详见前面的日志,其中很多宏无法通过UDF手册查阅, 蒸汽相中的质量源项的一般形式为: 式中: 通过一阶近似,热流之差可表达为: 式中: 通过此近似, ...

  2. JavaWeb之Tomcat(1) —— Tomcat的目录结构

    1. bin 文件夹 存放Tomcat的可执行文件 (1) startup.bat 文件,启动Tomcat服务的批处理文件. (2) shutdown.bat 文件,结束Tomcat服务的批处理文件. ...

  3. PC 端常用软件

    WPS  金山文档  有道云笔记  格式工厂 运行精灵 UC 遨游 360 Firefox 浏览器 光影魔术手 美图秀秀 2345好压 火绒安全软件 有道云笔记 悟空游戏厅 微信 QQ 迅雷 百度网盘 ...

  4. centos6安装vim插件youcompleteme问题及解决

    首先clone vim8代码库 git clone https://github.com/vim/vim.git 然后编译 注意下自己的python2.7config在哪儿 ./configure - ...

  5. 【转】反编译微信小程序错误: $gwx is not defined和__vd_version_info__ is not defined 已解决

    修改wxappUnpacker文件中的 wuWxss.js function runVM(name, code) { // let wxAppCode = {}, handle = {cssFile: ...

  6. Windows和Linux下putenv()函数导致composer更新失败

    bug复现: 原因: putenv() 函数设置特定的环境变量有可能是一个潜在的安全漏洞,所以这个函数在php配置文件中是默认禁止的,在 php.ini 中查找此函数,然后将此函数删除掉,重载配置即可 ...

  7. CMU Database Systems - Sorting,Aggregation,Join

    Sorting 排序如果可在内存里面排,用经典的排序算法就ok,比如快排 问题在于,数据表中的的数据是很多的,没法一下都放到内存里面进行排序 所以就需要用到,外排,多路并归排序 看下最简单的,2路并归 ...

  8. Could not attach to pid : "xx"最近启动Xcode运行项目都会出现这个问题,再次启动或者多启动几次,就可以正常运行工程了。

    最近启动Xcode运行项目都会出现这个问题,再次启动或者多启动几次,就可以正常运行工程了. 普及一下:PID(进程控制符)英文全称为Process Identifier,它也属于电工电子类技术术语. ...

  9. Linux防火墙白名单设置

    在linux系统中安装yum install iptables-services 然后 vi /etc/sysconfig/iptables # Generated by iptables-save ...

  10. 004-行为型-07-备忘录模式(Memento)

    一.概述 又叫做快照模式(Snapshot Pattern)或Token模式 保存对象的内部状态,并在需要的时候(undo/rollback)恢复对象以前的状态. 意图:在不破坏封装性的前提下,捕获一 ...