堆的数据结构
前言
这里整理一下堆有关的学习笔记,因为我在学习过程中经常苦于网上的讲解大多都直接放源码,让我苦于读枯燥的代码,所以我想把这些知识点用我自己的话表达出来(当然,为了严谨也会贴些源码),用以促进自己的理解,如果能帮到其他人那就更好了。
堆的初始化
首先来讲解一下堆的初始化和一些基础知识。
堆并不是程序运行时就存在的,而是在程序第一次进行堆块申请的时候(也可以先视为第一次调用malloc)才开始堆的初始化。
堆初始化时,会调用sbrk()申请一块内存,在这块内存用完前,你申请的堆块都会从这块内存中分割。随后会从这块内存中分出你malloc的堆块给你,剩下的就是top chunk。之后在你再次malloc的时候,如果没有空闲的堆块或者空闲的堆块大小不够的话,就会从top chunk的低地址割下来一块给你。因此,你获得的堆块都是物理相邻的。
堆块的数据结构
这里先贴一段源码
1 | /* |
好的看不懂没关系,我会详细解释的。
首先,堆块必须按2倍机器字长对齐,也就是说32位程序必须8字节对齐,64位必须16字节对齐。
0x0
每个堆块0x0的位置(占8个字节)是堆块的prev_size字段,如果与该堆块物理相邻的前一个堆块处于空闲状态,那这个地方用于存前一个堆块的大小,若前一个堆块处于使用中则这个地方用处不大,什么都不存。
0x8
在0x8的位置(占8个字节)是堆块的size字段,存放该堆块的大小,不过由于堆块都是16字节对齐的,也就是说,都是0x10的倍数,诶你会发现最后一个数永远是0,没有实际意义,所以,这一位数被额外地利用了起来,用来表示该堆块是否在使用中(依旧是inuse位),使用中就是1,空闲就是0(被free了的堆块就是空闲的堆块)。
由于每个堆块(chunk),都必须得有这两个共占16字节的字段,再加上chunk要求16字节对齐,所以chunk最小为32字节,哪怕你malloc申请的数字小于这个值,返回的chunk仍然会是32字节的(这里要注意,malloc函数的参数实际上是指一个堆块去除开头这16字节后的大小,也就是实际可以用的内存大小)。
0x10
从0x10这里开始,就是可正常使用的内存了,实际上malloc返回的指针就是指向这里。
不过这里要注意,很多初学者都会把指向堆块的指针和堆块搞混,需要弄明白的是,堆块并不会因为指向他的指针的改变而改变。
但是如果堆块被free了,处于空闲状态的话,就会根据堆块大小放进不同的bin中,而bin都是用链表来串联管理堆块的,所以0x10和0x18这两个位置就会放两个堆指针,fd和bk,分别指向bin链表中,该堆块的前一个和后一个堆块(注意不是物理相邻的前后)。
所以堆块说白了就是个内存块,你无论做什么操作,祂的地址都不会变,祂就在那里,无论是什么空闲状态还是使用状态,在哪个bin中本质上都是在堆块中写数据达成的,只不过有的数据仅仅只起到标识作用(inuse位),有的还有其他功能。
0x20
而最后,0x20这里,在绝大多数情况下都只是用来存数据,只有这个堆块被放进largebin时,这里会放两个指针fd_nextsize和bk_nextsize,这两个指针也是用来串联链表的,具体的功能我放在后面详细讲largebin的时候再细说。
后记
这里放张示意图来辅助理解。
这篇都是简单的知识点,本来还想再多写点,结果发现不管再写什么,想写清楚都少不了一番功夫,所以索性这篇就只写这么短了。




