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

  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. vmware exsi安装部署

    本文章参考:https://blog.csdn.net/fishinhouse/article/details/80980051 1.VMware-ESXi-6.5.0镜像下载 网盘链接:https: ...

  2. BurpSuite intuder里保存所有网页的特定内容:以bugku的cookies欺骗为例题

    这题里想读取index.php只能一行一行的读,通过控制line参数的值.如下: 正常的writeup都是写个爬虫,但我觉得burp肯定有自带这种功能,不用重造轮子.经过学习后发现以下步骤可以实现. ...

  3. 深度学习面试题18:网中网结构(Network in Network)

    目录 举例 参考资料 网中网结构通过多个分支的运算(卷积或池化),将分支上的运算结果在深度上连接 举例 一个3*3*2的张量, 与3个1*1*2的卷积核分别same卷积,步长=1, 与2个2*2*2的 ...

  4. 如何在团队中做好Code Review

    一.Code Review的好处 想要做好Code Review,必须让参与的工程师充分认识到Code Review的好处 1.互相学习,彼此成就 无论是高手云集的架构师团队,还是以CURD为主的业务 ...

  5. Python脚本基础运算和算法

    原文地址:https://www.cnblogs.com/ailiailan/p/10141741.html 通过关注“常见”脚本,是对代码的一个很好的学习和总结的方式. 1.冒泡排序 lis = [ ...

  6. 小D课堂 - 零基础入门SpringBoot2.X到实战_第9节 SpringBoot2.x整合Redis实战_37、分布式缓存Redis介绍

    笔记 1.分布式缓存Redis介绍      简介:讲解为什么要用缓存和介绍什么是Redis,新手练习工具          1.redis官网 https://redis.io/download   ...

  7. supervisord守护进程的使用

    原文链接:http://blog.csdn.net/xyang81/article/details/51555473 Supervisor(http://supervisord.org/)是用Pyth ...

  8. sql中去除重复的数据 select distinct * from table

    总的思路就是先找出表中重复数据中的一条数据,插入临时表中,删除所有的重复数据,然后再将临时表中的数据插入表中.所以重点是如何找出重复数据中的一条数据,有三种情况 1.重复数据完全一样,使用distin ...

  9. 安卓终端-Termux

    Termux是一个 Android 终端模拟器以及提供 Linux 环境的应用程序.跟许多其他应用程序不同,无需 root 设备也无需进行设置.它是开箱即用的!它会自动安装好一个最基本的 Linux ...

  10. Spring cloud微服务安全实战-4-10Zuul网关安全开发(三)

    首先把地址给它 发送post请求,请求的数据就是这个entity对象. 最后返回的值要封装到TokenInfo里面 如果一切正常的话就会拿到一个响应的实体,实体里面就包含了TokenInfo 打印实体 ...