# 堆相关数据结构
# chunk 大小问题
# 最小 chunk 大小、用户 malloc 申请大小和实际 malloc 分割出来的 chunk 大小的关系
众所周知,在 Glibc 源码中,malloc_chunk 的结构体的原码如下:
1 | struct malloc_chunk { |
这里面可以看到,如果一个 chunk 无论是处在 allocated 的状态下还是处于 free 状态下,他的 prev_size 和 size 这两个东西是必须存在的,而用户真正可以使用的空间是从 fd 那里开始的。
所以对于一个 chunk,它的实际大小其实是:这个 chunk 对应的存储数据的大小 + 2*size_t=size。
但是众所周知,chunk 存在下一个 chunk 的 prev_size 被复用到当前 chunk 里面用作存储数据。因此,我们可以算出用户实际可以使用的最大空间就是:当前 chunk 存储数据的空间大小 + size_t=size-size_t。
但是用户申请的大小是另一个概念。
众所周知,chunk 存在内存对齐的概念。下面是其在 Glibc 原码中的宏定义:
1 |
|
这里定义的 MALLOC_ALIGNMENT 就是规定了内存对齐的字节数。可以看到是 2 * SIZE_SZ。这里的 SIZE_SZ 就是 size_t,i386 下是 4 字节,x64 下是 8 字节。所以 32 位下对齐的是 8 字节,对应两个 p32 () 的大小,64 位下就是 16 字节,对应两个 p64 () 的大小。
所以堆管理器为了节省空间,会根据用户申请的空间堆实际申请空间大小进行调整,考虑内存对齐、内存复用等因素,我们大致可以推断出以下内容:
1. 当用户申请的内存小于 2 * size_t 的时候,就申请 2 * size_t 的大小的当前 chunk 存储数据的空间,加上内存复用的空间,实际就是可用 3 * size_t 的大小。
2. 当用户申请的大小刚好满足 2n * size_t < 申请 size <= 2n * size_t + size_t 的时候,实际上申请的当前 chunk 内的存储数据的空间只有 2n * size_t ,剩下的几个字节由复用的内存进行存储。
3. 当用户申请的大小满足 (2n+1) * size_t < 申请的内存 < (2n+2) * size_t 的时候,实际上申请的内存是 (2n+2) * size_t 的当前 chunk 存储数据的空间,因为要进行内存对齐。
这里给出对应的宏进行参考:
1 |
# 关于 bins 数组和 bin 结构的问题
我们知道,bin 实际上是一个双向循环链表,bins 数组将除了 fastbin 的 bin 的入口进行按照对应 bin 存储的 chunk 大小顺序进行串联,并且通过宏实现寻找对应大小 bin,之后找到遍历双向循环链表的入口。
# bins 数组结构
我们首先看看 bins 数组的结构。
我们首先看看在 malloc_state 中 bins 数组的声明:
1 |
|
可以看到 bins 数组申请的大小是 254 个 mchunkptr。也就是存储 127 个 bin,也就是 127 和双向循环链表的入口。此时我们大致了解到,bins 数组中对应每个 bin 占用 bins 数组两个 mchunkptr,即成对出现。
以下是关于 bins 的几个宏:
1 | typedef struct malloc_chunk *mbinptr; |
这里的 m 就是 malloc_state,i 就是要查找的对应 bin 的下标(这里的下标并不是 bins 数组下标,在这里就得到了体现。)
# bins 中 bin 的存储与访问
我们可以看到,bin_at 尝试去寻找 bin 的时候,取到的下标永远是偶数,因此我们可以知道,bin 在 bins 数组里面是连续相邻成对出现的。即:
bin [0],bin [1] 是一个 bin
bin [2],bin [3] 是一个 bin
bin [4],bin [5] 是一个 bin
,当然这里 bins 数组存储的只是每个 bin 的入口,或者可以说是末尾,毕竟是双向循环链表。
我们知道,一个 bin 的双向循环链表的开头是一个 front(是一个 chunk),这个 front 对应的就是 bins 数组那一对。为了节省空间,这里 front 仅仅保留了 fd、bk 这两个元素,对应的就是 bins [2n]、bins [2n+1]。
用图表画出来就是:
注意这里的指针并不是图中显示的那样。
实际上,这里的每一个 fd 都连接到下一个 chunk 的开头,即 prev_size 那里。同样所有的 bk 都是连接到前一个 chunk 的 prev_size 那里。
具体可以自己写几个 chunk 试验一下。
bins 数组中的 bin 有三种类型:unsortedbin,smallbin,largebin,他们对应的位置是:
第 1 个 :unsorted bin
第 2 ~ 63 个:small bin
第 64 ~ 127 个:large bin
# largebin 独自的双向循环链表
这里只有 largebin 启用了 fd_nextsize,bk_nextsize 这两个元素,具体连接方式:
largebin 每个 bin 选出对应大小的代表元,这些代表元用 fd_nextsize,bk_nextsize 连成一个双向循环链表。
# bins 数组总结
这里总结一下 bins 数组:
# fastbin
说了 bins,顺便该说说不存在于 bins 数组的 bin---fastbin(虽然这个挺好理解的)
结构就是相当于单向链表版本的 bins 数组,malloc_state 中的定义就是:
1 | typedef struct malloc_chunk *mfastbinptr; |
直接放个图就啥都明白了,这个没啥难度。
# Conclusion
这篇笔记着重澄清了 bins 数组和 bin 之间的关系以及存储结构等内容,顺便写了一下 fastbin 的结构。
这部分内容对于部分 Glibc 教程来说基本上写的不是不详细就是不易理解。这里仅仅作为本人理解的内容,如有错误,速速指出!!!!!!!。
# ByeBye ༼ つ ◕_◕ ༽つ
༼ つ ◕_◕ ༽つ༼ つ ◕_◕ ༽つ༼ つ ◕_◕ ༽つ༼ つ ◕_◕ ༽つ༼ つ ◕_◕ ༽つ
༼ つ ◕_◕ ༽つ༼ つ ◕_◕ ༽つ༼ つ ◕_◕ ༽つ༼ つ ◕_◕ ༽つ༼ つ ◕_◕ ༽つ
༼ つ ◕_◕ ༽つ༼ つ ◕_◕ ༽つ༼ つ ◕_◕ ༽つ༼ つ ◕_◕ ༽つ༼ つ ◕_◕ ༽つ
༼ つ ◕_◕ ༽つ༼ つ ◕_◕ ༽つ༼ つ ◕_◕ ༽つ༼ つ ◕_◕ ༽つ༼ つ ◕_◕ ༽つ
༼ つ ◕_◕ ༽つ༼ つ ◕_◕ ༽つ༼ つ ◕_◕ ༽つ༼ つ ◕_◕ ༽つ༼ つ ◕_◕ ༽つ