浅析STL allocator

  一般而言,我们习惯的 C++ 内存配置操作和释放操作是这样的:

1 class FOO{};
2 FOO *pf = new FOO;    
3 delete pf;

  我们看其中第二行和第三行,虽然都是只有一句,当是都完成了两个动作。但你 new 一个对象的时候两个动作是:先调用::operator new 分配一个对象大小的内存,然后在这个内存上调用FOO::FOO()构造对象。同样,当你 delete 一个对象的时候两个动作是:先调用FOO::~FOO() 析构掉对象,再调用::operator delete将对象所处的内存释放。为了精密分工,STL 将allocator决定将这两个阶段分开。分别用 4 个函数来实现:

  1.内存的配置:alloc::allocate();

  2.对象的构造:::construct();

  3.对象的析构:::destroy();

  4.内存的释放:alloc::deallocate();

  其中的 construct() 和 destroy()定义在 STL的库文件中,源代码如下:

 1 template <class T>
 2 inline void destroy(T* pointer) {
 3     pointer->~T();                               //只是做了一层包装,将指针所指的对象析构---通过直接调用类的析构函数
 4 }
 5 
 6 template <class T1, class T2>
 7 inline void construct(T1* p, const T2& value) {
 8   new (p) T1(value);                            //用placement new在 p 所指的对象上创建一个对象,value是初始化对象的值。
 9 }
10 
11 template <class ForwardIterator>                //destory的泛化版,接受两个迭代器为参数
12 inline void destroy(ForwardIterator first, ForwardIterator last) {
13   __destroy(first, last, value_type(first));    //调用内置的 __destory(),value_type()萃取迭代器所指元素的型别
14 }
15 
16 template <class ForwardIterator, class T>
17 inline void __destroy(ForwardIterator first, ForwardIterator last, T*) {
18   typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;
19   __destroy_aux(first, last, trivial_destructor());        //trival_destructor()相当于用来判断迭代器所指型别是否有 trival destructor
20 }
21 
22 
23 template <class ForwardIterator>
24 inline void                                                //如果无 trival destructor ,那就要调用destroy()函数对两个迭代器之间的对象元素进行一个个析构
25 __destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) {
26   for ( ; first < last; ++first)
27     destroy(&*first);
28 }
29 
30 template <class ForwardIterator>                        //如果有 trival destructor ,则什么也不用做。这更省时间
31 inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) {}
32 
33 inline void destroy(char*, char*) {}          //针对 char * 的特化版
34 inline void destroy(wchar_t*, wchar_t*) {}    //针对 wchar_t*的特化版

  看到上面这么多代码,大家肯定觉得 construct() 和 destroy() 函数很复杂。其实不然,我们看到construct()函数只有几行代码。而 destroy() 稍微多点。但是这么做都是为了提高销毁对象时的效率。为什么要判断迭代器所指型别是否有 trival destructor,然后分别调用不同的执行函数?因为当你要销毁的对象很多的时候,而这样对象的型别的destructor 都是 trival 的。如果都是用__destroy_aux(ForwardIterator first, ForwardIterator last, __false_type)来进行销毁的话很费时间,因为没必要那样做。而当你对象的destructor 都是 non-trival 的时候,你又必须要用__destroy_aux(ForwardIterator first, ForwardIterator last, __false_type)来析构。所以,我们要判断出对象型别的destructor 是否为 trival,然后调用不同的__destroy_aux。

  说完 construct() 和 destory() ,我们来说说 alloc::allocate() 和 alloc::deallocate(),其源代码在 <stl_alloc.h>中。stl_alloc.h中代码设计的原则如下:

  1.向 system heap 要求空间

  2.考虑多线程状态

  3.考虑内存不足时的应变措施

  4.考虑过多“小型区块”可能造成的内存碎片问题。

  stl_alloc.h中的代码相当复杂,不过没关系。我们今天只看其中的allocate() 和 deallocate()。在讲这两个函数之前,我们还必须来了解一下SGI  STL(SGI限定词是STL的一个版本,因为真正的STL有很多不同公司实现的版本,我们所讨论的都是SGI版本) 配置器的工作原理:

  考虑到小型区块可能造成内存破碎问题(即形成内存碎片),SGI STL 设计了双层级配置器。第一层配置器直接使用malloc() 和 free().第二层配置器则视情况采用不同的策略:但配置区块超过 128 bytes时,调用第一级配置器。当配置区块小于 128 bytes时,采用复杂的 memory pool 方式。下面我们分别简单的介绍一下第一级和第二级配置器:

第一级配置器 _ _malloc_alloc_template:

  由于第一级配置器的配置方法比较简单,代码也容易理解,我在这里全部贴出:

 1 //以下是第第一级配置器
 2 template <int inst>
 3 class __malloc_alloc_template {
 4 
 5 private:
 6 
 7 //以下函数用来处理内存不足的情况
 8 static void *oom_malloc(size_t);
 9 
10 static void *oom_realloc(void *, size_t);
11 
12 static void (* __malloc_alloc_oom_handler)();
13 
14 public:
15 
16 static void * allocate(size_t n)
17 {
18     void *result = malloc(n);                    //第一级配置器,直接使用malloc()
19     //如果内存不足,则调用内存不足处理函数oom_alloc()来申请内存
20     if (0 == result) result = oom_malloc(n);
21     return result;
22 }
23 
24 static void deallocate(void *p, size_t /* n */)
25 {
26     free(p);            //第一级配置器直接使用 free()
27 }
28 
29 static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz)
30 {
31     void * result = realloc(p, new_sz);            //第一级配置器直接使用realloc()
32     //当内存不足时,则调用内存不足处理函数oom_realloc()来申请内存
33     if (0 == result) result = oom_realloc(p, new_sz);
34     return result;
35 }
36 
37 //设置自定义的out-of-memory handle就像set_new_handle()函数
38 static void (* set_malloc_handler(void (*f)()))()
39 {
40     void (* old)() = __malloc_alloc_oom_handler;
41     __malloc_alloc_oom_handler = f;
42     return(old);
43 }
44 };
45 
46 template <int inst>    
47 void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0;  //内存处理函数指针为空,等待客户端赋值
48 
49 template <int inst>
50 void * __malloc_alloc_template<inst>::oom_malloc(size_t n)
51 {
52     void (* my_malloc_handler)();
53     void *result;
54 
55     for (;;) {                                                     //死循环
56         my_malloc_handler = __malloc_alloc_oom_handler;            //设定自己的oom(out of memory)处理函数
57         if (0 == my_malloc_handler) { __THROW_BAD_ALLOC; }         //如果没有设定自己的oom处理函数,毫不客气的抛出异常
58         (*my_malloc_handler)();                                    //设定了就调用oom处理函数
59         result = malloc(n);                                        //再次尝试申请
60         if (result) return(result);
61     }
62 }
63 
64 template <int inst>
65 void * __malloc_alloc_template<inst>::oom_realloc(void *p, size_t n)
66 {
67     void (* my_malloc_handler)();
68     void *result;
69 
70     for (;;) {
71         my_malloc_handler = __malloc_alloc_oom_handler;
72         if (0 == my_malloc_handler) { __THROW_BAD_ALLOC; }    //如果自己没有定义oom处理函数,则编译器毫不客气的抛出异常
73         (*my_malloc_handler)();                                //执行自定义的oom处理函数
74         result = realloc(p, n);                                //重新分配空间
75         if (result) return(result);                            //如果分配到了,返回指向内存的指针
76     }
77 }

  上面代码看似繁杂,其实流程是这样的:

  1.我们通过allocate()申请内存,通过deallocate()来释放内存,通过reallocate()重新分配内存。

  2.当allocate()或reallocate()分配内存不足时会调用oom_malloc()或oom_remalloc()来处理。

  3.当oom_malloc() 或 oom_remalloc()还是没能分配到申请的内存时,会转如下两步中的一步:

    a).调用用户自定义的内存分配不足处理函数(这个函数通过set_malloc_handler() 来设定),然后继续申请内存!

    b).如果用户未定义内存分配不足处理函数,程序就会抛出bad_alloc异常或利用exit(1)终止程序。

  看完这个流程,再看看上面的代码就会容易理解多了!

第二级配置器 _ _default_alloc_template:

  第二级配置器的代码很多,这里我们只贴出其中的 allocate() 和 dellocate()函数的实现和工作流程(参考侯捷先生的《STL源码剖析》),而在看函数实现代码之前,我大致的描述一下第二层配置器配置内存的机制。

  我们之前说过,当申请的内存大于 128 bytes时就调用第一层配置器。当申请的内存小于 128bytes时才会调用第二层配置器。第二层配置器如何维护128bytes一下内存的配置呢? SGI 第二层配置器定义了一个 free-lists,这个free-list是一个数组,如下图:

  

  这数组的元素都是指针,用来指向16个链表的表头。这16个链表上面挂的都是可以用的内存块。只是不同链表中元素的内存块大小不一样,16个链表上分别挂着大小为

   8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128 bytes的小额区块,图如下:

   

  就是这样,现在我们来看allocate()代码:

 static void * allocate(size_t n){obj * __VOLATILE * my_free_list;obj * __RESTRICT result;//要申请的空间大于128bytes就调用第一级配置if (n > (size_t) __MAX_BYTES) {return(malloc_alloc::allocate(n));}//寻找 16 个free lists中恰当的一个my_free_list = free_list + FREELIST_INDEX(n);result = *my_free_list;if (result == 0) {//没找到可用的free list,准备新填充free listvoid *r = refill(ROUND_UP(n));return r;}*my_free_list = result -> free_list_link;return (result);};

  其中有两个函数我来提一下,一个是ROUND_UP(),这个是将要申请的内存字节数上调为8的倍数。因为我们free-lists中挂的内存块大小都是8的倍数嘛,这样才知道应该去找哪一个链表。另一个就是refill()。这个是在没找到可用的free list的时候调用,准备填充free lists.意思是:参考上图,假设我现在要申请大小为 56bytes 的内存空间,那么就会到free lists 的第 7 个元素所指的链表上去找。如果此时 #7元素所指的链表为空怎么办?这个时候就要调用refill()函数向内存池申请N(一般为20个)个大小为56bytes的内存区块,然后挂到 #7 所指的链表上。这样,申请者就可以得到内存块了。当然,这里为了避免复杂,误导读者我就不讨论refill()函数了。allocate()过程图如下:

  

  学过链表的操作的人不难理解上图,我就不再讲解。下面看deallocate(),代码如下:

 1 static void deallocate(void *p, size_t n)
 2 {
 3     obj *q = (obj *)p;
 4     obj * __VOLATILE * my_free_list;
 5 
 6     //如果要释放的字节数大于128,则调第一级配置器
 7     if (n > (size_t) __MAX_BYTES) {
 8         malloc_alloc::deallocate(p, n);
 9         return;
10     }
11     //寻找对应的位置
12     my_free_list = free_list + FREELIST_INDEX(n);
13     //以下两步将待释放的块加到链表上
14     q -> free_list_link = *my_free_list;    
15     *my_free_list = q;
16 }

  deallocate()函数释放内存的步骤如下图:


  其实这就是一个链表的插入操作,也很简单。不再赘述!上面忘了给链表结点的结构体定义了,如下:

union obj{union obj * free_list_link;char client_date[1]; 
};

  至此,SGI STL的对象的构造与析构、内存的分配与释放就介绍完毕了。

 

转载于:https://www.cnblogs.com/zhuwbox/p/3699977.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/432622.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

十六进制除法运算法则_苏教版数学七年级上册 微课视频 2.6 有理数的乘法与除法(1)...

第一章《数学与我们同行》视频讲解 同步练习2.1 《正数与负数》2.2 有理数与无理数2.3 数轴2.4 绝对值与相反数(1)2.4 绝对值与相反数(2)2.5 有理数的加法与减法(1)2.5 有理数的加法与减法(2)2.6 有理数的乘法与除法(1)七、有理数的乘除法1.有理数的乘法法则法则一&#xff1…

a5d27 emmc启动 修改1

a5d27第1级bootloader是从sdhc0(emmc)加载还是从sdhc1(sd卡)加载&#xff0c; 只需要修改board/sama5d2_xplained文件即可 修改CONFIG_SDHC* y 这个宏定义在board/sama5d2_xplained.c中的void at91_sdhc_hw_init(void)函数实现 从上面代码可以看出第1级的bootloader只支持一…

矩阵每一行重复_【剑指offer】65 矩阵中的路径

- 题目描述请设计一个函数&#xff0c;用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始&#xff0c;每一步可以在矩阵中向左&#xff0c;向右&#xff0c;向上&#xff0c;向下移动一个格子。如果一条路径经过了矩阵中的某一…

mysql5.464位下载_MySQL Front 64位

MySQL-Front是一款实用的MYSQL数据库管理工具&#xff0c;软件自带了简体中文语言&#xff0c;与mysql数据库连接后就可以对其地蚝各类管理操作了&#xff0c;比如对域进行编辑、增加和删除&#xff0c;执行sql脚本或者导出数据库等操作&#xff0c;除此之外还可以将数据库保存…

a5d27 第1级bootloader启动问题

drivers/sdhc.c里的 static int sdhc_set_clock(struct sd_card *sdcard, unsigned int clock) 这段代码总是会超时&#xff0c;造成启动失败。 log如下图

LDA-Latent Dirichlet Allocation 学习笔记

以下内容主要基于《Latent Dirichlet Allocation》,JMLR-2003一文&#xff0c;另加入了一些自己的理解,刚开始了解&#xff0c;有不对的还请各位指正。 LDA-Latent Dirichlet Allocation JMLR-2003 摘要&#xff1a;本文讨论的LDA是对于离散数据集&#xff0c;如文本集&#xf…

app 图标规格参考表

转自&#xff1a;http://www.cocoachina.com/appstore/top/2012/1105/5031.html 像我一样记不住iOS应用图标像素尺寸的开发者不在少数&#xff0c;我经常需要查询不同设备上的应用尺寸&#xff0c;为了方便自己、方便大家&#xff0c;我制作了下面的图表供大家参考。 iPhone、i…

mysql数据库开启远程连接_安装MySQL数据库并开启远程访问

一、安装MySQL数据库MySQL安装在系统盘下(C:\Program Files)&#xff0c;方便系统备份。1.双击安装程序&#xff0c;勾选“I accept the license terms”&#xff0c;点击“Next”按钮。2.检测需要的安装&#xff0c;直接点击Next。3.按照下图所示步骤安装。4.设置密码&#xf…

flume mysql hdfs_利用Flume将MySQL表数据准实时抽取到HDFS

一、为什么要用到Flume在以前搭建HAWQ数据仓库实验环境时&#xff0c;我使用Sqoop抽取从MySQL数据库增量抽取数据到HDFS&#xff0c;然后用HAWQ的外部表进行访问。这种方式只需要很少量的配置即可完成数据抽取任务&#xff0c;但缺点同样明显&#xff0c;那就是实时性。Sqoop使…

一种解决运行程序报“应用程序配置不正确”的问题

在我们开发工程中&#xff0c;可能有些情况下&#xff0c;不能在本机进行调试。这个时候我们一般会使用VM&#xff08;vmware)建立一个虚拟机环境&#xff0c;然后把编译过的程序放在该虚拟机环境下执行调试。可是在某些情况下&#xff0c;不管我们编译的是debug还是release版本…

mysql+ubunt+绿色安装_Mysql在ubuntu18上的安装及简单使用

数据相关行业都离不开数据库&#xff0c;mysql在ubuntu上的安装比在windows上安装简单多了&#xff0c;下面我记录一下自己成功安装的步骤和使用。1.安装软件首先更新一下源&#xff1a;sudo apt-get update然后安装mysql服务器端&#xff1a;sudo apt-get install mysql-serve…

sql字符串拼接_Mybatis的SqlSession执行sql过程

上一篇分析了SqlSession执行sql的过程&#xff0c;其中并没有分析sql是从哪里来的&#xff0c;今天就来仔细分析下。Sql来源从上一篇的最后一步执行sql那里倒推sql的来源&#xff0c;源码主要过程如下图&#xff1a;可以看到最后是通过BoundSql直接获取的sql&#xff0c;然后往…

深入浅出FSUIPC的作用以及使用方法

看此贴前您需要掌握的技能或知识&#xff1a;1. 有FSX或FS2004并正确安装了FSUIPC 2. 具备一定的C语言理解能力&#xff0c;C语言是一切高级语言的基础&#xff0c;单片机主要也用的C语言。 3. 掌握以下几种编程语言之一即可&#xff1a; VC\VB\C#\DELPHI\JAVA\CMFC\.NET版的C或…

在 VC6 中使用 GdiPlus-安装

安装三部曲&#xff1a; Step1&#xff1a;下载 GdiPlus SDK 文件包&#xff1b; 链接地址1&#xff1a;http://www.codeguru.com/code/legacy/gdi/GDIPlus.zip 链接地址2&#xff1a;http://www.codersource.net/samples/mfcgdiplus.zip Step2&#xff1a;安装&#xff1b; &a…

ASP.NET MVC 的多国语系支持

ASP.NET MVC 的多国语系支持 posted on 2014-05-14 11:31 stickout 阅读(...) 评论(...) 编辑 收藏 转载于:https://www.cnblogs.com/linhui/p/3727364.html

aliyun centos6 安装mysql_阿里云CentOS6.8安装MySQL5.6

1、使用SSH Secure Shell工具连接阿里云服务器2、使用SSH Secure File Transfer工具上传MySQL压缩包3、解压MySQL压缩包到指定目录(需要在先/usr/local下创建mysql目录)进入压缩文件存放位置&#xff0c;进行解打包&#xff1a;tar -xvf MySQL-5.6.22-1.el6.i686.rpm-bundle.ta…

用Javascript获取页面元素的位置

制作网页的过程中&#xff0c;你有时候需要知道某个元素在网页上的确切位置。 下面的教程总结了Javascript在网页定位方面的相关知识。 一、网页的大小和浏览器窗口的大小 首先&#xff0c;要明确两个基本概念。 一张网页的全部面积&#xff0c;就是它的大小。通常情况下&#…

[Qt] 利用QtWebKit完成JavaScript访问C++对象

http://blog.csdn.net/longsir_area/article/details/42965565 一. 介绍 在浏览器扩展或者WebApp的项目经常用的脚本语言JavaScript有很多局限性&#xff0c;比如&#xff0c;javascript语言不能够夸窗口访问js对象&#xff0c;不能直接读写磁盘文件&#xff08;这个…

mysql三大范式_MySQL学习笔记

1、数据库结构设计1、总-总体流程图2、分-【提取属性】业务分析评价的属性:{用户&#xff0c;课程主标题&#xff0c;内容&#xff0c;综合评分&#xff0c;内容实用&#xff0c;简洁易懂&#xff0c;逻辑分析&#xff0c;发布时间} 问答评论属性&#xff1a;{类型&#xff0c;…

QT webkit 各个类之间关系--QWebView-QWebPag

一、QT webkit简介 1.Qt Qt&#xff08;发音同 cute&#xff09;是一个跨平台的C应用程式开发框架&#xff0c;有时又被称为C部件工具箱。Qt被用在KDE桌面环境、Opera、Google Earth、Skype、Adobe Photoshop Album和VirtualBox的开发中。它是挪威Qt Software 的产品&#xff0…