堆基础(未完,持续更新)
堆利用入门
堆管理器
堆概述
堆的概念
堆是虚拟内存空间的一段连续的线性区域,提供动态分配的内存,允许程序申请大小未知的内存。再用户与操作系统之家按,作为动态内存管理的中间人,响应程序的申请内存请求,向操作系统申请内存,然后返回给程序。并且管理用户所释放的内存,适时归还给操作系统。
各种堆管理器
- dlmalloc -General Purpose allocator
- ptmalloc2 - glibc
- jemalloc - FreeBSD and Firefox
- tcmalloc - Google libumem - Solaris
堆管理器并非由操作系统实现,而是由libc.os.6链接库实现的。该库封装了一些系统调用,位用户提供了方便的动态内存分配接口的同时,力求高效地管理由系统调用申请来的内存。
申请内存的系统调用
- brk
- mmap
Areana
内存分配区,可以理解为堆管理器所持有的内存池。
对管理器与程序的内存交易发生在arena中,可以理解为对管理器向操作系统批发来的由冗余的内存库存。
操作系统 -> 堆管理器 -> 程序
物理内存 -> arena -> 可用内存
chunk
概述
程序申请内存的单位,也是堆管理器中管理内存的基本单位。malloc()函数返回的指针指向一个chunk的数据区域。
chunk的具体实现
struct malloc_chunk {
INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T mchunk_size; /* Size in bytes, including overhead. */
/* Doubly linked -- used only if free. */
struct malloc_chunk* fd; /* Forward pointer to next chunk in list. */
struct malloc_chunk* bk; /* Back pointer to previous chunk in list. */
/* Doubly linked -- used only if free and in a large bin. */
struct malloc_chunk* fd_nextsize; /* Forward pointer to next larger chunk in list. */
struct malloc_chunk* bk_nextsize; /* Back pointer to previous larger chunk in list. */
};
chunk的分类
按状态
- malloced:已经分配且填写了数据的chunk。
- free:被释放掉的malloced chunk成为free chunk。
按大小
- fast
- small
- large
- tcache
按特定功能
- top chunk:arena中从未被使用过的内存区域。
- last remainder chunk:free大堆块之后重新用malloc分割chunk后剩余的部分。
堆的大小对齐
堆的大小必须是MALLOC_ALIGNMENTd的整数倍,如果申请的大小不是MALLOC_ALIGNMENT的整数倍,会被转换成满足条件的最小的值。32 位系统中, MALLOC_ALIGNMENT 可能是 4 或 8 ;64 位系统中,MALLOC_ALIGNMENT 是 8或 16。我们可以发现,不管size的大小如何变,size的低三位都为0,为了不浪费这三个比特位,他们从高到低分别用来表示(AMP):
- NON_MAIN_ARENA:记录当前chunk是否不属于主线程,1表示不属于,0表示属于。
- IS_MAPPED:记录当前chunk是否由mmap分配。
- PREV_INUSE:记录前一个chunk块是否被分配。
chunk的微观结构
chunk结构的分析
- prev_sizee:若前一个物流相邻的chunk是free chunk,泽尔表示其大小。否则被用于储存前一个chunk的数据。
- size:前三个比特位存放AMP占据一字长的低3比特以后的地址,用于表示当前chunk的大小(整个chunk的大小,包括 chunk头)。
-A flag:NON_MAIN_ARENA:记录当前chunk是否不属于主线程,1表示不属于,0表示属于。
-M flag:IS_MAPPED:记录当前chunk是否由mmap分配。
-P flag:PREV_INUSE:记录前一个chunk块是否被分配。一般来说,堆中第一个被分配的内存size字段的P为都会被设置为1,以便于防止访问前面的非法内存。当一个chunk的size的P位为0时,我们能通过prev_size字段来获取上一个chunk的大小以及地址。这方便chunk之间的合并。 - fd pointer:bin中指向下一个(不一定物理相邻)空闲的chunk。
- bk pointer:bin中指向上一个(不一定物理相邻)空闲的chunk。
- fd_nextsize:large bin中指向前一个与当前chunk大小不同的第一个空闲块(不包含bin的头指针)。
- bk_nextsize:large bin中指向后一个与当前chunk大小不同的第一个空闲块(不包含bin的头指针)。
Bin
为了方便理解,下面将用一下代码演示下面提到一部分bin
#include <stdlib.h>
#include <malloc.h>
int main ()
{
mallopt(1,0);
int *a = malloc(0x10);
int *b = malloc(0x10);
int *c = malloc(0x10);
free(b);
free(a);//fastbin
mallopt(1,0);// 修改global_max_fast = 0, 进入 unsorted bin
malloc(0x50);//进入small bin
return 0;
}
bin的概述
管理arena中空闲的chunk的机构,以数组的形式存在,数组元素为相应大小的chunk链表的链表头,存在于arena的malloc_state中。
bin
的分类
fast bins
单向链表,管理16、24、32、40、48、56、64 Bytes的free chunks(32位下默认)其中的chunk的in_use位(下一个物理相邻的chunk的P位)总是为1,LIFO(Last In, First Out)。
unsorted bin
双向链表结构,管理刚刚释放还未分类的chunk。
关闭fastbin,再查看bin的情况。
我们再申请大于0x40空间的堆块。刚才unsortedbin中的堆块就会回归small bin。
small bins
下面使用以下代码进行演示
#include <stdio.h>
int main ()
{
int *a = malloc(0x10);
int *b = malloc(0x10);
int *c = malloc(0x10);
int *d = malloc(0x10);
int *e = malloc(0x20);
int *f = malloc(0x20);
free(a);
free(c);
free(e);
int *g = malloc(0x400);
return 0;
}
bins[2] ~ bins[63],62个循环双向链表,FIFO机制,管理16、24、32、40....504 Bytes的free chunks(32位)每个链表中存储的chunk大小都一致。
large bins
使用一下代码演示
#include <stdio.h>
int main ()
{
int *a = malloc(0x400);
int *b = malloc(0x410);
int *c = malloc(0x420);
int *d = malloc(0x430);
int *e = malloc(0x440);
int *f = malloc(0x450);
free(a);
free(c);
free(e);
malloc(0x500);
return 0;
}
bins[64]~bins[126],63个循环双向链表,FIFO机制,管理大于504Bytes的free chunkss(32位)每个链表中存贮的chunk大小再一个范围内。
(tcache)glibc-2.24以后
堆分配策略
Malloc
根据程序申请的内存大小以及相应大小的chunk通常使用的频度(fastbin chunk、small chunk、large chunk)依次实现不同的分配方法。它由小到大一次检查不同的bin中是否由相同相应的空闲块可以满足用户请求的内存。当所有空闲的chunk都无法满足时,他会考虑top chunk。当top chunk也无法满足时,堆分配器会进行内存块申请。
free
将程协暂时不用的chunk回收给堆管理器,适当的时候把内存归还操作系统。根据chunk的大小来优先试图把free chunk链如tcache或者fast bin,不满足则链入unsorted bin并将其中的物理相邻的free chunk合并,将相应大小的chunk分类放入small bin或large bin中。除了tcache chunk和fast bin chunk以外,其他的chunk在free时会与其物理地址相邻的free chunk合并。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?