STL源码剖析——空间配置器Allocator#3 自由链表与内存池

  上节在学习第二级配置器时了解了第二级配置器通过内存池与自由链表来处理小区块内存的申请。但只是对其概念进行点到为止的认识,并未深入探究。这节就来学习一下自由链表的填充和内存池的内存分配机制。

refill()函数——重新填充自由链表

  前情提要,从上节第二级配置器的源码中可以看到,在空间配置函数allocate()中,当所需的某号自由链表为空时,才会调用refill()函数来填充链表。refill()函数默认申请20块区块的内存(5行),但所得内存不一定就是20块,要看当前内存池的剩余情况和堆容量的情况,这个在学习chunk_alloc()函数时会详解讨论,然后将第零块返回给allocate()函数,allocate()函数再返回给真正申请内存的用户,剩余内存分成区块串接成自由链表。

 1 template <bool threads, int inst>
 2 void* __default_alloc_template<threads, inst>::refill(size_t n)
 3 {
 4      int nobjs = 20;    //需要填充自由链表时,尝试分配20个区块作为自由链表的新结点
 5      char * chunk = chunk_alloc(n, nobjs);    //交给chunk_alloc去分配内存,后述
 6      obj * __VOLATILE * my_free_list;
 7      obj * result;
 8      obj * current_obj, * next_obj;
 9      int i;
10      if (1 == nobjs) return(chunk);        //如果只分配到1个区块,则直接返回给申请者,自由链表无新结点
11      my_free_list = free_list + FREELIST_INDEX(n);
12      /* Build free list in chunk */
13      result = (obj *)chunk;    //如果分配到大于1个区块,即将第0个区块返回给申请者,剩下的作为自由链表的空闲区块
14     *my_free_list = next_obj = (obj *)(chunk + n);    //从新分配内存的第一个区块开始串接成自由链表
15      for (i = 1; ; i++) {
16          current_obj = next_obj;
17          next_obj = (obj *)((char *)next_obj + n);
18          if (nobjs - 1 == i) {
19              current_obj -> free_list_link = 0;
20              break;
21          } else {
22              current_obj -> free_list_link = next_obj;
23          }
24      }
25      return(result);
26 }          

chunk_alloc()函数——内存池

  其实内存池仅仅是靠三个静态成员变量来管理的,并没有多复杂的机制:

static char *start_free;    //内存池起始指针
static char *end_free;      //内存池的尾指针
static size_t heap_size;    //多次调用内存池, 就会在内存池中分配更多的内存, 这就是一个增量.

  这在上节的第二级配置器源码中也有提到。真正复杂的是为内存池的分配内存和自由链表与内存池的交互上,这两个在chunk_alloc()上有所体现。

 1 template <bool threads, int inst>
 2 char*
 3 __default_alloc_template<threads, inst>::chunk_alloc(size_t size, int&
 4     nobjs)        //注意nobjs是个引用
 5 {
 6     char * result;
 7     size_t total_bytes = size * nobjs;        //计算要申请的内存大小
 8     size_t bytes_left = end_free - start_free;    //计算当前内存池剩余大小
 9     if (bytes_left >= total_bytes) {        //如果当前内存池大小大于等于申请的大小,直接返回所需内存
10         result = start_free;
11         start_free += total_bytes;
12         return(result);
13     }
14     else if (bytes_left >= size) {            //如果内存池没有足够的大小,但能够供应一个以上的区块,修正能够提供的区块个数
15         nobjs = bytes_left / size;
16         total_bytes = size * nobjs;
17         result = start_free;
18         start_free += total_bytes;
19         return(result);
20     }
21     else {                                    //内存池连一个区块的大小都无法提供了,计算准备向堆申请的内存大小
22         size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
23         // 如果当前内存池要有剩余,寻找哪号自由链表能收了它.
24         if (bytes_left > 0) {
25             obj * __VOLATILE * my_free_list =
26                 free_list + FREELIST_INDEX(bytes_left);
27             ((obj *)start_free)->free_list_link = *my_free_list;
28             *my_free_list = (obj *)start_free;
29         }
30         start_free = (char *)malloc(bytes_to_get);    //向堆申请空间补充内存池
31         if (0 == start_free) {        //堆空间也不足了,malloc()失败
32             int i;
33             obj * __VOLATILE * my_free_list, *p;
34                 //尝试查看更大号的自由链表中是否有1个空闲块能供我们使用
35             for (i = size; i <= __MAX_BYTES; i += __ALIGN) {
36                 my_free_list = free_list + FREELIST_INDEX(i);
37                     p = *my_free_list;
38                 if (0 != p) {        //如果有,把它从自由链表中划进内存池,并返回递归调用chunk_alloc()函数的结果,修正nobjs
39                     *my_free_list = p->free_list_link;
40                     start_free = (char *)p;
41                     end_free = start_free + i;
42                     return(chunk_alloc(size, nobjs));    //在递归调用chunk_alloc后,会发现内存池拥有了大于1个区块的内存了,可以把该区块返回给refill()函数
43                 }
44             }
45             //堆空间没内存,其它自由链表也没内存,山穷水尽
46             end_free = 0;
47             start_free = (char *)malloc_alloc::allocate(bytes_to_get);    //调用第一级配置器,期望out-of-memory机制能否尽点力
48         }
49         heap_size += bytes_to_get;    //记录内存池已经开辟的堆大小
50         end_free = start_free + bytes_to_get;
51         return(chunk_alloc(size, nobjs));
52     }
53 }

  总结上述源码的功能,以end_free - start_free来判断内存池大小是否足够,如果充足则直接调出20个区块返回给自由链表。如果不足20个区块但还足够供应一个以上的区块,就拔出这不足20个区块的空间出去。这时pass by reference的nobjs将被修改为实际能够供应的区块数。如果内存池连一个区块空间都无法提供,此时便需利用malloc(),从堆中配置内存,为内存池注入足够的内存以应付需求,这足够的内存(22行)是指需求量的两倍再加上随着配置次数增加而越来越大的附加量(heap_size),如果能通过malloc()申请到40块内存,就将第前20块给自由链表,后20块放进内存池备用。

  举个实际的例子,让我们对这段源码有更深刻的认识:假设程序一开始,客端调用chunk_alloc(32, 20),于是malloc()配置40个32bytes区块,其中第一个交出,另外19个交给free_list[3]维护,剩余的20个留给内存池。接下来客端调用chunk_alloc(64, 20),此时free_list[7]空空如也,必须向内存池要求支持。内存池只够供应(32*60)/64=10个64bytes区块,就把这10个区块返回,第1个交给客端,余下交由free_list[7]维护。此时内存池空空如也。再调用chunk_alloc(96, 20),free_list[11]也空空如也,必须向内存池要求支持,而内存池表示爱莫能助,只能再向堆申请40+n个96bytes区块的内存,其中第1个交出,另外19个交给free_list[11]维护,余20+n个区块留给内存池......

  万一真的山穷水尽,整个堆空间都不够了,malloc()失败,chunk_alloc()就四处寻找有无“有未用且足够大区块”的自由链表。找到了就挖一块交出,找不到就调用第一级配置器。第一级配置器也使用malloc()来配置内存,但它有out-of-memory处理机制,或许有机会释放其它内存拿来此处使用。如果可以就成功,不可以就发出bad_alloc异常。

原文地址:https://www.cnblogs.com/MisakiJH/p/11655111.html

时间: 2024-12-12 02:50:29

STL源码剖析——空间配置器Allocator#3 自由链表与内存池的相关文章

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

前言 以STL的实现角度而言,第一个需要介绍的就是空间配置器,因为整个STL的操作对象都存放在容器之中. 你完全可以实现一个直接向硬件存取空间的allocator. 下面介绍的是SGI STL提供的配置器,配置的对象,是内存.(以下内容来自<STL源码剖析>) 空间配置器的标准接口 根据STL的规范,allocator的必要接口 各种typedef 1 allocator::value_type 2 allocator::pointer 3 allocator::const_pointer 4

STL源码剖析——空间配置器Allocator#2 一/二级空间配置器

上节学习了内存配置后的对象构造行为和内存释放前的对象析构行为,在这一节来学习内存的配置与释放. C++的内存配置基本操作是::operator new(),而释放基本操作是::operator delete().这两个全局函数相当于C的malloc() 和free() 函数.而SGI正是以malloc() 和free() 完成内存的配置与释放. 考虑到小型区块可能造成的内存破碎问题,SGI设计了两级的空间配置器.第一级直接使用malloc() 和free() ,而第二级则视情况采用不同的策略:当

STL源码剖析 --- 空间配置器 std::alloc

STL是建立在泛化之上的.数组泛化为容器,参数化了所包含的对象的类型.函数泛化为算法,参数化了所用的迭代器的类型.指针泛化为迭代器,参数化了所指向的对象的类型.STL中的六大组件:容器.算法.迭代器.配置器.适配器.仿函数. 这六大组件中在容器中分为序列式容器和关联容器两类,正好作为STL源码剖析这本书的内容.迭代器是容器和算法之间的胶合剂,从实现的角度来看,迭代器是一种将operator*.operator->.operator++.operator-等指针相关操作予以重载的class tem

STL源码剖析--空间配置器

STL的设计非常巧妙,组件间互取短长,形成了一个世界,这是这个世界里的组件: 1. containers(容器):所谓容器,是指存放数据的地方,将数据以一定的方法组织存放.根据不同的组织方式,可以把容器分为顺序容器,如vector.deque.list,关联容器,如set.map.Container是一种class template. 2. algorithm(算法):各种常用不常用的算法如sort.copy.search等等.algorithm是一种function template. 3.

STL源码分析--空间配置器的底层实现 (二)

STL源码分析-空间配置器 空间配置器中门道 在STL中的容器里都是使用统一的空间配置器,空间配置器就是管理分配内存和销毁内存的.在STL将在heap空间创建一个对象分为两个步骤,第一是申请一块内存,第二是在这块内存中初始化一个对象.首先申请空间是由malloc提供,初始化一个对象时由constructor管理.销毁一个对象也是由两步骤完成,第一是销毁空间上的对象,第二是释放这块内存. 同时,STL的空间配置器分为两级内存,如果申请的内存空间大于128KB,那么就使用第一级空间配置,如果小于,那

STL源码分析--空间配置器 第一级配置器

一.SGI STL配置器简介 SGI STL的配置器与众不同,它与标准规范不同.如果要在程序中明确使用SGI配置器,那么应该这样写: [cpp] view plaincopyprint? vector<int,std::alloc> iv; 他的名字是alloc,而且不接受任何参数.标准配置器的名字是allocator,而且可以接受参数. SGI STL的每一个容器都已经指定了缺省配置器:alloc.我们很少需要自己去指定空间配置器.比如vector容器的声明: [cpp] view plai

STL源码剖析——空间的配置与释放

C++的内存配置基本操作是  ::operator new(),内存释放的基本操作是 ::operator delete().这两个全局函数相当于C的malloc()和free()函数.是的,正是如此,STL正是以malloc()和free()完成内存的配置与释放. 但是考虑到小型区块所可能造成的内存破碎问题,STL中设计了双层级配置器, 第一级配置器直接使用malloc()和free(),第二级配置器则视情况采用不用的策略:当配置区块超过128bytes时,视之为"足够大",便调用第

STL源码剖析---根据最新版本的g++4.9.0(支持C++11)的修订(1)空间配置器

源码剖析采用的G++版本为2.91.57版本,是比较老的版本与最新版本4.9.0有某些方面的差别.现在我针对最新版本做一个分析.我下载了最新的gcc-4.9.0的包作为观察对象: 我们#include <>时的头文件放在:gcc-4.9.0/libstdc++-v3/include/std:例如vector. 真正的实现文件放在:gcc-4.9.0/libstdc++-v3/include/bits:例如:stl_vector,注意前面的stl_. 最后要说的是:技术是不断进步,不断发展变化的

《STL源码剖析》空间配置器

空间配置器(allocator) 空间配置器按我的理解就是C++ STL进行内存管理的组件(包括内存的申请和释放):当然,不只是内存,还可以向硬盘申请空间: 我主要看了内存的配置与释放(这里"配置"就应该是"申请"的意思).STL对此设计的哲学主要包括以下四个方面: 1.向系统堆空间申请内存空间 2.考虑了多线程的情况下的申请: 3.考虑内存不足的应变措施: 4.考虑过多"小型区块"的内存碎片的问题: C++ 申请的基本动作是::operator