malloc()在哪里分配内存?是进程虚拟地址空间的数据段还是堆段?
核心概念澄清:堆、数据段与malloc的运行逻辑
为什么栈分配不需要系统调用,堆分配有时需要?
进程创建时,操作系统会预先为栈段划分好固定范围的虚拟地址空间(Linux下默认8MB),且提前完成地址映射。活动记录入栈出栈仅修改栈指针寄存器,只要操作在预设的栈空间范围内,就不会触发操作系统干预,自然不需要系统调用。
但堆的逻辑完全不同:进程启动时,操作系统并没有为堆预先分配大量可用的虚拟地址空间。你想要使用某段堆地址,必须先向操作系统申请获得这段地址的访问权限,否则直接访问会触发段错误。
malloc 作为C标准库的函数,本身确实会通过空闲链表、已分配链表自行管理内存:如果当前已经从操作系统申请到的堆内存中有符合大小的空闲块,malloc 会直接拆分返回,全程在用户态完成,没有系统调用开销。只有当现有空闲块不足以满足分配需求时,malloc 才会发起系统调用向操作系统申请更多内存,加入自己的内存池后再分配。
「数据段」与「堆」的概念歧义来源
你看到的两种说法并不冲突,只是不同语境下「数据段」的定义不同:
- 狭义的编译/链接视角的数据段:就是你提到的
.data段,专门存储已初始化的全局变量、静态局部变量,加上相邻的.bss段(存储未初始化的全局/静态变量),大小在编译时就已经固定,进程加载时由操作系统完成分配。 - 广义的Unix系统调用视角的数据段:指从程序可执行代码段结束位置开始,到
brk指针(堆的当前上边界)为止的连续虚拟地址区域,它包含了上述狭义的.data、.bss段,往上的增长区域就是我们常说的「堆」。
sbrk/brk 系统调用描述里的「修改数据段大小」,指的就是修改广义数据段的上边界 brk 指针,本质就是扩展堆的可用空间。常见的进程地址空间示意图里,.data/.bss段和堆是连续向高地址增长的,早期Unix系统把这整块连续区域统一称为数据段管理,就导致了这个概念歧义。
malloc的完整运行逻辑梳理
我们可以把标准实现的 malloc 完整流程拆解为以下几步:
- 进程启动完成初始化后,
brk指针默认指向.bss段的末尾,也就是堆的起始地址,此时C标准库的内存池还没有可用的堆内存。 - 调用
malloc时,首先遍历内存池的空闲链表,查找大小匹配的空闲块:找到就直接拆分、标记为已分配后返回地址,全程无系统调用。 - 空闲块不足以满足需求时,按分配大小走不同逻辑:
- 分配内存小于阈值(一般为128KB,不同实现可配置):调用
sbrk向高地址移动brk指针,申请一块较大的内存加入内存池,再拆分分配给用户。 - 分配内存大于阈值:直接调用
mmap系统调用申请一段独立的匿名虚拟地址空间,直接返回给用户,不加入内存池的空闲链表管理。
- 分配内存小于阈值(一般为128KB,不同实现可配置):调用
- 调用
free释放内存时,仅将对应内存块标记为空闲,放回空闲链表,不会立刻归还操作系统。只有当堆末尾存在连续的大块空闲内存时,才会调用sbrk回退brk指针,将这部分内存归还给操作系统。
K&R 书中的 malloc 实现属于早期简化版本,仅用 sbrk 申请内存,没有加入 mmap 分配大内存的逻辑,和上面的核心逻辑是完全一致的。
内容的提问来源于stack exchange,提问作者Abhishek Ghosh
相关产品推荐
相关产品推荐

