c++ 学习笔记之 malloc

文章目录

  • 一、内存布局
  • 二、brk(sbrk)和mmap函数
    • [1. brk(sbrk)](#1. brk(sbrk))
    • [2. mmap](#2. mmap)
    • [3. ptmalloc](#3. ptmalloc)
      • [3.1 chunk(内存块基本结构)](#3.1 chunk(内存块基本结构))
      • [3.1 malloc分配大体流程](#3.1 malloc分配大体流程)
      • [3.3 释放流程](#3.3 释放流程)

一、内存布局

由上图可知,栈至顶向下扩展,堆至底向上扩展。

二、brk(sbrk)和mmap函数

1. brk(sbrk)

在 Linux 系统中,malloc底层主要通过brk、sbrk和mmap这几个系统调用来实现内存的分配和管理。

cpp 复制代码
#include <unistd.h>
int brk( const void *addr )
void* sbrk ( intptr_t incr );

两者的作用是扩展heap的上界brk

Brk()的参数设置为新的brk上界地址,成功返回1,失败返回0;如果addr大于当前的程序中断点,就会扩大数据段,分配新的内存;如果addr小于当前的程序中断点,就会缩小数据段,释放内存。

Sbrk()的参数为申请内存的大小,返回heap新的上界brk的地址;当increment为正数时,堆顶指针会向高地址移动,分配内存;当increment为负数时,堆顶指针会向低地址移动,释放内存 。

2. mmap

cpp 复制代码
#include <sys/mman.h>
void *mmap(void *addr, size\_t length, int prot, int flags, int fd, off\_t offset);
int munmap(void *addr, size_t length);

mmap()进行内存分配(malloc)时一般使用后者,前者主要是进行文件映射。

mmap分配内存比较直接,相对的开销也较大,释放也比较简单,,通过munmap函数可以立即将内存归还给操作系统。

一般来说,当malloc申请的内存较小时,会使用brk或sbrk来扩展堆内存;而当申请的内存较大时(通常阈值为 128KB),会直接使用mmap在内存映射区申请内存 。

但是,如果每次申请内存都调用这些接口的话,势必会影响系统的性能,并且也极容易产生内存碎片。所以malloc采用ptmalloc(内存池管理机制)对内存的分配与回收进行管理。

3. ptmalloc

ptmalloc会预先向操作系统申请一块内存供用户使用,当我们申请和释放内存的时候,ptmalloc会将这些内存管理起来,并通过一些策略来判断是否将其回收给操作系统。

3.1 chunk(内存块基本结构)

在ptmalloc中,内存是由一个个chunk组成,每个chunk由结构体malloc_chunk进行描述:

cpp 复制代码
struct malloc_chunk {
    INTERNAL_SIZE_T prev_size;  // 前一个chunk的大小(如果前一个chunk是空闲的)
    INTERNAL_SIZE_T size;       // 当前chunk的大小,包括头部开销
    struct malloc_chunk *fd;    // 双向链表指针,指向下一个空闲chunk(仅当chunk空闲时使用)
    struct malloc_chunk *bk;    // 双向链表指针,指向上一个空闲chunk(仅当chunk空闲时使用)
    // 其他字段,如用于管理大内存块的指针等
     /*当前的 chunk 存在于 large bins 中时使用 */  
  struct malloc_chunk* fd_nextsize;      /*仅当chunk空闲时使用 */  
  struct malloc_chunk* bk_nextsize;     /*仅当chunk空闲时使用 */  
};

prev_size: 表示前一个空闲的chunk大小,如果前一个 chunk 不空闲,该字段无意义,prev_size主要用于相邻空闲chunk的合并。

size :当前 chunk 的大小和一些其他信息,其中低三位(A,M,P)中记录包括前一个 chunk 是否在使用中,当前 chunk 是否是通过 mmap 获得的内存,当前 chunk 是否属于非主分配区。

fd 和 bk : 这两兄弟只有当该 chunk 块空闲时才存在,其作用是用于将对应的空闲 chunk 块加入到空闲chunk 块链表中进行管理。例如,当一个chunk被释放时,它会根据size字段中的信息找到前一个和后一个chunk,判断它们是否空闲,如果空闲则进行合并,然后将合并后的chunk通过这两个指针插入到相应的空闲链表中 。如果该 chunk 块被分配给了应用程序使用,那么这两个指针被当作应用程序的使用空间,不会浪费。

fd_nextsize 和 bk_nextsize: 和fd、bk类似。

当chunk为空时才有fd、bk、fd_nextsize、bd_nextsize四个指针,当chunk不为空,这四个指针的空间是直接交给用户使用的。

3.1 malloc分配大体流程

  1. 搜索空闲链表:首先,ptmalloc 会在快速链表(fast bins)(小于4 字节)、小链表(small bins)和大链表(large bins)中查找是否有合适大小的空闲chunk。快速链表用于管理小且常用大小的chunk,这些chunk在释放时不会与相邻的chunk合并,分配速度很快;小链表中的chunk大小固定且不超过 512 字节,按大小顺序排列;大链表中的chunk大小大于 512 字节 。如果在这些链表中找到了合适的chunk,就直接返回该chunk给用户 。
  2. 扩展堆或使用 mmap:如果在空闲链表中没有找到合适的chunk,ptmalloc 会尝试从堆顶的top chunk中分配内存。(top chunk相当于分配区的顶部空闲内存)如果top chunk的大小足够,就从top chunk中分割出一块满足需求的内存返回给用户,剩余部分成为新的top chunk;如果top chunk的大小不足,且申请的内存小于一定阈值(如 128KB),ptmalloc 会调用sbrk扩展堆内存,然后从新扩展的内存中分配;如果申请的内存大于阈值,ptmalloc会使用mmap将内存放到mmaped chunk上,当释放mmaped chunk上的内存的时候会直接交还给操作系统。
  3. 大块拆分:当从大链表中找到一个大于请求大小的chunk时,ptmalloc 会将该chunk拆分成两部分,一部分满足请求大小返回给用户,另一部分作为剩余块(remainder chunk),根据其大小插入到合适的空闲链表中 。

3.3 释放流程

当调用free释放内存时,ptmalloc 会执行以下操作:

  1. 标记为空闲:首先将释放的chunk标记为空闲状态,并根据chunk的大小和标志位判断是否需要与相邻的空闲chunk进行合并 。
  2. 合并相邻空闲块:如果当前chunk的前一个和后一个chunk都是空闲的,ptmalloc 会将它们合并成一个大的chunk,减少内存碎片 。合并后的chunk会被插入到合适的空闲链表中 。
  3. 缩小堆:如果释放的chunk与top chunk相邻,且合并后的top chunk足够大(超过一定阈值),ptmalloc 会调用sbrk缩小堆内存,将多余的内存归还给操作系统 。
相关推荐
薛不痒14 小时前
深度学习之优化模型(数据预处理,数据增强,调整学习率)
深度学习·学习
昵称已被吞噬~‘(*@﹏@*)’~15 小时前
【RL+空战】学习记录03:基于JSBSim构造简易空空导弹模型,并结合python接口调用测试
开发语言·人工智能·python·学习·深度强化学习·jsbsim·空战
苦藤新鸡15 小时前
8.最长的无重复字符的子串
c++·力扣
我想我不够好。15 小时前
学到的知识点 1.8
学习
李派森15 小时前
软考高项(信息系统项目管理师)—第4章 信息系统管理全解析
笔记·计算机网络
꧁Q༒ོγ꧂16 小时前
C++ 入门完全指南(四)--函数与模块化编程
开发语言·c++
旖旎夜光16 小时前
Linux(9)
linux·学习
汉克老师16 小时前
GESP2025年12月认证C++八级真题与解析(判断题8-10)
c++·快速排序··lcs·gesp八级·gesp8级
qq_4335545416 小时前
C++ manacher(求解回文串问题)
开发语言·c++·算法