brk和sbrk是Linux下用于调整program break(堆顶)的系统调用/库函数:brk()设绝对地址,成功返回0,失败仅返-1且无详细错误;sbrk()以增量方式操作并返回原brk值,其中sbrk(0)是安全可靠的只读用法;二者均需页对齐,负向收缩在glibc中限制严格、不可靠;malloc等标准库函数避免频繁调用它们,而是批量申请内存并内部管理空闲块,小分配走brk路径(<128KB),大分配直接使用mmap。

brk 和 sbrk 是 Linux 下直接操作堆边界的系统调用 / 库函数,但它们不能随意“调整”——只能单向扩展(主流用法),收缩有严格限制,且在现代 C++ 程序中基本不该手动调用。
brk() 只能设目标地址,且失败不报错细节
brk() 接收一个绝对地址参数,把 program break 强制设为该位置。成功返回 0,失败返回 -1,但不会告诉你为什么失败(比如超出 RLIMIT_DATA、地址未对齐、或低于初始 brk)。
- 必须传入页对齐地址(通常是
getpagesize()的整数倍),否则行为未定义 - 若传入地址低于当前
brk,内核可能拒绝,也可能静默接受但后续sbrk(0)返回值异常 - 多次调用
brk()不会累积效果:它总是覆盖,不是增量 - 示例:
brk(sbrk(0) + 4096)扩展一页;但brk(sbrk(0) - 4096)在多数 glibc 环境下实际无效,free()后的内存也不会归还给内核
sbrk() 增量更安全,但负增量仍不可靠
sbrk() 是 C 库封装,比 brk() 更常用,返回前一个 brk 地址,便于链式判断。但它本质仍是调用 brk(),负增量(收缩)在生产环境极不推荐。
-
sbrk(0)是唯一被广泛认可的“只读”用法,用于获取当前堆顶,开销低 -
sbrk(n)(n > 0)一般可靠,但若 n 过大(如 > 128KB),glibc 的malloc可能已切到mmap()分配路径,此时sbrk不再参与管理 -
sbrk(-n)行为依赖 libc 实现:glibc 中它尝试收缩,但仅当收缩后地址 ≥ 初始brk且无中间mmap区域时才可能生效;否则返回(void*)-1 - 嵌入式裸机或自研分配器中才可能依赖负
sbrk,通用应用请放弃
为什么 malloc 不靠它频繁扩堆?
因为每次 brk/sbrk 都触发一次系统调用,代价高(上下文切换 + TLB 刷新)。malloc 实际采用“批处理”策略:
立即学习“C++免费学习笔记(深入)”;
- 首次小内存请求时,
malloc用sbrk()向内核申请一大块(如 128KB),自己维护空闲链表 - 后续
malloc/free都在该块内完成,不碰brk - 只有当前批次耗尽,才再次
sbrk()批量申请;而free后的内存通常不立即sbrk收缩,避免抖动 - 超过阈值(默认 128KB)的分配直接走
mmap(MAP_ANONYMOUS),完全绕过brk机制
调试时怎么确认 brk 状态?
别信 sbrk(0) 单次返回值,要结合 /proc/self/maps 观察真实映射范围:
- 运行中执行
cat /proc/<pid>/maps | grep "\[heap\]",看起始/结束地址 -
sbrk(0)返回值应落在该区间内,且接近结束地址;若明显偏小,说明有未释放的mmap区域插在中间 - 注意 ASLR:开启时初始
brk有随机偏移,&end(bss 结尾)不再等于初始brk - 用
strace -e brk,sbrk,mmap,munmap ./a.out可直接看到哪些调用真正发生了
真正需要手动调 brk/sbrk 的场景极少——除非你在写轻量级内存池、调试分配器 bug,或跑在无 libc 的嵌入式环境。否则,信任 malloc 的决策,把精力放在减少小对象分配、复用缓冲区、或改用 mmap 大块内存上更实际。


















