brk 与 sbrk 是 Unix/Linux 进程控制数据段(堆)边界的底层系统调用与库函数,是 malloc/free 等高级分配器的基础。
#include <unistd.h>
int brk(void *addr); // 将数据段末尾设为 addr
void *sbrk(intptr_t increment); // 将数据段末尾移动 increment 字节,返回旧末尾地址
sbrk(0) 可获取当前堆顶地址(即 program break)。
进程地址空间(32 位示例):
高地址
+-----------------+ <-- 栈顶(向下增长)
| 栈 |
| |
| 共享库/映射 | <-- mmap 区域
| |
| 堆 | <-- brk/sbrk 控制此处边界
| |
+-----------------+ <-- 数据段末尾 (program break)
| BSS / Data |
| 代码段 |
低地址
内核用 mm_struct 中的 start_brk(堆起始)和 brk(堆当前末尾)管理堆区域。brk 系统调用仅修改 mm_struct.brk,不立即分配物理页;物理页在首次访问时通过缺页中断分配(懒分配)。
malloc 是用户态分配器(glibc ptmalloc2),维护空闲链表/红黑树等数据结构。malloc 调用 brk/sbrk 向内核申请扩大堆(主分配区),或 mmap 创建新映射(大块/非主分配区)。free 释放块回收到分配器空闲链表,通常不立即归还内核(避免频繁系统调用),仅在顶部连续大块空闲时触发 malloc_trim 缩回 brk。类比:brk 是“批发”向内核要大块内存,malloc 是“零售”切分给应用。
| 特性 | brk/sbrk | mmap |
|---|---|---|
| 地址空间 | 堆区连续增长 | 任意虚拟地址映射 |
| 文件映射 | 不支持 | 支持文件/设备/匿名映射 |
| 碎片 | 仅向上增长,释放困难 | 可独立 munmap 释放 |
| 大块分配 | 不适合(难以归还) | 默认 >128KB 走 mmap |
| 线程安全 | 需锁保护全局 brk | 每次调用独立,天然友好 |
现代分配器(ptmalloc, jemalloc, tcmalloc)综合使用两者:小块用 brk 池,大块用 mmap 直映。
malloc/new 申请,程序员控制生命周期,灵活但易出错(泄漏、越界、双重释放)。| 函数 | 作用 | 初始化 | 备注 |
|---|---|---|---|
malloc(n) | 分配 n 字节 | 不初始化 | 内容不定 |
calloc(n, sz) | 分配 n*sz 字节 | 全 0 | 整数/指针安全为 0/NULL,浮点数不保证 |
realloc(p, n) | 调整大小 | 保留原数据,新增部分不定 | 可能移动内存 |
free(p) | 释放 | - | p 必须来自 malloc 系,释放后置 NULL |
alloca(n) | 栈上分配 | 不初始化 | 函数返回自动释放,慎用大块 |
sbrk(inc) | 移动堆顶 | - | 极少直接使用 |
valgrind --leak-check=full、AddressSanitizer (编译加 -fsanitize=address)、mtrace。malloc 即写对应 free,RAII 封装,所有权清晰。brk/sbrk 是操作系统提供的最原始堆扩展接口,理解其工作原理有助于深入掌握 malloc 实现、排查内存碎片、优化高性能分配器。日常开发请直接使用成熟的分配器(malloc/new/智能指针),仅在编写分配器、调试内存腐败或极端性能调优时接触底层接口。