第3篇-C堆内存管理-原理篇

前言

我前面一篇详细介绍了堆内存管理的有关概念,你往下读该篇的内容,我确信你已经阅读了我前面2篇有关堆内存管理的随笔。

本系列文章并不是malloc/free的api说明书,如果你对C/C++的内存管理一无所知,建议请搞懂了再往下阅读,我自己简书主页有个专辑专门就是归纳《C/C++内存管理》的有关编程技术。
还有下面这个图是贯穿整个C/C++堆内存管理的所有知识点。所以这个图要牢记在大脑中。

现在我们知道了动态内存分配的基础,那么现在看看如何在实现过程中,我们要解决那些问题呢?

  1. 对于给定的指针,free函数如何知道要释放的内存尺寸?
  2. 我们如何跟中堆内随机分布的闲置区块?
  3. 当堆中大量闲置区块可以满足上层应用的mallc请求,那么如何快速找到一个尺寸合适闲置区块返回给上层应用。
  4. 当分配区域时,找到的闲置区块比请求的尺寸数量要打,那么额外多额外空间,我们如何处理?
  5. 如何将free操作后的堆内存返回给堆空间?

解决方案

首先这里解决第一个提出的问题,通常在分配内存时,我们会在有效负载的的前一个overhead写入当前分配区域的长度,这个区块的前置overhead通常被称为内存块的头部(Heap Block Header 或简称 header),如下图所示。

需要注意的是,响应每个malloc请求的已分配区块都需要一个头部信息。虽然这会带来空间开销,但这是值的,举个例子,当调用malloc(5)时,如上图指针p2指向的是payload的首个字节,因为指针p2是返回给上层应用程序,因此payload的内存块对于内存程序是可见的。而payload的前一个字(4字节)的块,malloc会默认设置该payload的头字段,并且将该已分配区块尺寸写入该头字段,因此当我们尝试使用将给定的指针传递给free函数,free函数会读取该头字段,就知道需要释放的内存的尺寸有多大。

备注:上图的mallc(5)的内存申请数量和该已分配区块的头字段内的尺寸数相差1,因为头字段占用一个

下一个 问题就是是如何跟中堆内随机分布的闲置区块?在C标准的堆分配器的实现中存在两种方法

  • 隐式链表(Implict List),分配器的底层逻辑通过一个内部链表将所有区块的头字段链接起来,并且遍历该链表,查看这些区域的头部字段的尺寸以及状态(未分配,已分配),甚至可以根据区块的尺寸进行排序,因为我们总是可以读取堆上的区块的头字段信息
  • 显式链表(Explicit List):包含堆中所有闲置区块的链表

当然还有其他复杂的方法:

  • 隔离闲置的链表(Segregated Free List):可以跟踪不同尺寸的置区块之间的链表,也可以说可以跟踪闲置区块之间是已分配的区块
  • 根据尺寸已排序的链表(Blocks Sorted List by sizes):可以使用平衡二叉树,指针位于每个空闲块中,长度用作关key值。

其实上面的后三种方法,都是从都一种典型的方法变种变种而来,链表的节点就是每个目标区块的头一个字的字段信息。因此他们的空间消耗其实差别是不大的。唯一区别实现时,优先遍历闲置区块,还是遍历已分配区块,还是两者同时都遍历。但普遍的高效实现是使用上述的最后一种。我们不妨从最经典原型的隐式链表说起.

隐式链表

对于堆空间中的每个区块,我们需要知道两件事情就是区块尺寸状态,而我们每个区块头一个字,在x86_64的就是8个字节的头字段,完全足够容纳这两个信息,下面是头字段的基本信息。

头字段格式
  • 在x86_64下是按8字节对齐的,低地址的位总以0填充,因此我们可以充分利用这些填充位作为区块状态的标记,即已分配(allocated)为1,否则为0
  • 头字段的高地址位就用来存储尺寸,或者你可以在添加其他统计信息

这里还有一个需要注意的是,当我们实现的malloc读取区块的头字段时,必须对状态标记位做屏蔽处理。

下图是一个8字节(2个字)对齐的隐式链表的堆内存块示例


现在我们来解答一下前文的遗留问题
Q: 隐式链表如何知道到达堆的末端?
A:这个我们在末端定义一个尺寸空间为0并且已分配的头节点作为标示即可。

Q:如何将free操作后的堆内存返回给堆空间?
A::其实,这个很简单对于给定的指针p,因为指针p指向的是payload的首个字节,我们只要对指针p执行移位操作移动到状态标记位,将allocated的标记从1改为0即可。

Q:对于malloc请求,那么如何快速找到一个尺寸合适闲置区块返回给上层应用?
A:通过while循环遍历链表,找到状态标记为0并且每次迭代头字段比较size和内存申请数量n,如果size>n大就返回该未分配区块的内存地址。但隐式链表由于没有对闲置区块按照尺寸进行排序,那么返回的闲置块区可能会比内存数大得多,此时会造成堆空间的资源浪费。

举个例子malloc(4)的请求,并且该请求后续不会增长,由于隐方链表由于先找到A会返回A的闲置区块而不是B,显然请求数4远远比闲置区块数9少得多,而B的闲置区块数是恰好是4,我们从资源利用是应该选择B才合理的。


未排序的链表会在分配内存块时,可能造成内存块利用率下降

显然解决这个问题就是要对隐式链表中的未分配的区块头字段按照尺寸数执行升序排列,才能解决这个。例如下图


升序排列后的链表

继续更新.....

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342