堆栈brk指针

本文详细解析了操作系统内存分配原理,包括进程如何通过brk和mmap系统调用分配虚拟内存,以及标准C库malloc和free函数背后的机制。通过一个实例,展示了malloc在不同内存需求下如何选择分配方式,以及内存释放与内存紧缩的过程。

内存分配的原理

从操作系统角度来看,进程分配内存有两种方式,分别由两个系统调用完成:brk和mmap(不考虑共享内存)。

1、brk是将数据段(.data)的最高地址指针_edata往高地址推;

2、mmap是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存

     这两种方式分配的都是虚拟内存,没有分配物理内存在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。


在标准C库中,提供了malloc/free函数分配释放内存,这两个函数底层是由brk,mmap,munmap这些系统调用实现的。


下面以一个例子来说明内存分配的原理:

情况一、malloc小于128k的内存,使用brk分配内存,将_edata往高地址推(只分配虚拟空间,不对应物理内存(因此没有初始化),第一次读/写数据时,引起内核缺页中断,内核才分配对应的物理内存,然后虚拟地址空间建立映射关系),如下图:

内存分配的原理__进程分配内存有两种方式,分别由两个系统调用完成:brk和mmap(不考虑共享内存)。 - 无影 - 专注、坚持、思索
 
1、进程启动的时候,其(虚拟)内存空间的初始布局如图1所示。
      其中,mmap内存映射文件是在堆和栈的中间(例如libc-2.2.93.so,其它数据文件等),为了简单起见, 省略了内存映射文件。
      _edata指针(glibc里面定义)指向数据段的最高地址。 
2、进程调用A=malloc(30K)以后,内存空间如图2:
      malloc函数会调用brk系统调用,将_edata指针往高地址推30K,就完成虚拟内存分配。
      你可能会问: 只要把_edata+30K就完成内存分配了?
      事实是这样的,_edata+30K只是完成虚拟地址的分配, A这块内存现在还是没有物理页与之对应的, 等到进程第一次读写A这块内存的时候,发生缺页中断,这个时候,内核才分配A这块内存对应的物理页。 也就是说,如果用malloc分配了A这块内容,然后从来不访问它,那么,A对应的物理页是不会被分配的。 
3、
进程调用B=malloc(40K)以后,内存空间如图3。

情况二、malloc大于128k的内存,使用mmap分配内存,在堆和栈之间找一块空闲内存分配(对应独立内存,而且初始化为0),如下图:

内存分配的原理__进程分配内存有两种方式,分别由两个系统调用完成:brk和mmap(不考虑共享内存)。 - 无影 - 专注、坚持、思索
4、进程调用C=malloc(200K)以后,内存空间如图4:
      默认情况下,malloc函数分配内存,如果请求内存大于128K(可由M_MMAP_THRESHOLD选项调节),那就不是去推_edata指针了,而是利用mmap系统调用,从堆和栈的中间分配一块虚拟内存
      这样子做主要是因为::
      brk分配的内存需要等到高地址内存释放以后才能释放(例如,在B释放之前,A是不可能释放的,这就是内存碎片产生的原因,什么时候紧缩看下面),而mmap分配的内存可以单独释放。
      当然,还有其它的好处,也有坏处,再具体下去,有兴趣的同学可以去看glibc里面malloc的代码了。 
5、进程调用D=malloc(100K)以后,内存空间如图5;
6、进程调用free(C)以后,C对应的虚拟内存和物理内存一起释放。
内存分配的原理__进程分配内存有两种方式,分别由两个系统调用完成:brk和mmap(不考虑共享内存)。 - 无影 - 专注、坚持、思索
7、进程调用free(B)以后,如图7所示:
        B对应的虚拟内存和物理内存都没有释放,因为只有一个_edata指针,如果往回推,那么D这块内存怎么办呢
当然,B这块内存,是可以重用的,如果这个时候再来一个40K的请求,那么malloc很可能就把B这块内存返回回去了。 
8、进程调用free(D)以后,如图8所示:
        B和D连接起来,变成一块140K的空闲内存。
9、默认情况下:
       当最高地址空间的空闲内存超过128K(可由M_TRIM_THRESHOLD选项调节)时,执行内存紧缩操作(trim)。在上一个步骤free的时候,发现最高地址空闲内存超过128K,于是内存紧缩,变成图9所示。
参考:
<think>好的,用户想知道在Linux环境下,使用C++中的new运算符是否会调用brk函数。我需要结合系统调用和内存管理的知识来解答这个问题。 首先,我得回忆一下new运算符在C++中的底层实现。通常,new会调用malloc来分配内存,而malloc的实现依赖于C标准库,比如glibc。这时候应该考虑不同大小的内存分配策略。对于小内存块,glibc的malloc可能会使用brk或sbrk系统调用来调整程序的数据段大小,扩大堆空间。而对于大块内存,可能会使用mmap来申请内存映射区域,这样更高效且避免碎片问题。[^2][^4] 接下来,需要确认brk和mmap的区别。brk通过移动堆的结束地址来扩展或收缩堆,适合小内存的频繁分配。而mmap可以匿名映射大块内存,适合一次性分配较大的内存,释放时直接归还给系统,减少碎片。[^2] 然后,用户的问题涉及到具体实现,可能需要查看glibc的源代码或者相关文档。例如,当调用new时,如果分配的内存较小,glibc会从现有的堆空间中分配,可能不需要调用brk;但如果堆空间不足,就会调用brk扩展堆。对于大内存,glibc会优先使用mmap,这样就不会调用brk。[^1][^2] 还要考虑不同版本的glibc是否有不同的行为,以及操作系统的内存管理策略是否会影响这一点。比如,某些情况下,即使分配小内存,如果碎片较多,可能也会使用mmap。 此外,用户可能需要实际验证的方法,比如使用strace跟踪系统调用,或者通过GDB调试查看调用堆栈。例如,用strace跟踪一个使用new的程序,观察是否出现brk或mmap的调用。[^3] 最后,总结回答时需要明确说明new是否会调用brk取决于分配的大小和glibc的实现策略,同时提供验证方法。可能还需要提到替代方法如Valgrind来检测内存使用情况。</think>在 Linux 环境下,C++ 的 `new` 运算符是否调用 `brk` 系统调用,取决于具体的内存分配策略和分配的内存大小。以下是逐步分析: --- ### 1. **`new` 运算符的底层机制** C++ 的 `new` 运算符最终会调用 C 标准库的内存分配函数 `malloc`,而 `malloc` 的实现由 C 库(如 glibc)决定。具体行为如下: - **小内存分配**:通常使用 `brk`/`sbrk` 系统调用扩展程序的堆空间(heap),通过移动堆顶指针实现[^4]。 - **大内存分配**:通常使用 `mmap` 系统调用直接映射匿名内存页,以避免堆空间碎片化[^2]。 --- ### 2. **`brk` 与 `mmap` 的区别** - **`brk` 的作用**:调整堆的结束地址,适用于频繁的小块内存分配(如几十 KB 以内)。 - **`mmap` 的作用**:直接映射虚拟内存页(通常以 4KB 为单位),适合大块内存分配(如超过 `MMAP_THRESHOLD`,默认 128KB)。 --- ### 3. **验证方法** 可以通过以下方式观察 `new` 是否触发 `brk`: 1. **使用 `strace` 跟踪系统调用**: ```bash strace -e brk,mmap ./your_program ``` 观察输出中是否出现 `brk` 调用[^2]。 2. **通过 `malloc` 调试信息**: 设置环境变量 `MALLOC_TRACE`,使用工具分析内存分配行为: ```bash export MALLOC_TRACE=/tmp/malloc.log ./your_program mtrace ./your_program /tmp/malloc.log ``` --- ### 4. **结论** - 如果通过 `new` 分配的内存较小且连续,glibc 会优先通过 `brk` 扩展堆空间。 - 如果分配的内存较大(超过阈值),或堆空间碎片化严重,则会通过 `mmap` 分配内存[^4]。 --- ### 示例代码分析 ```cpp int* p = new int[100]; // 小内存,可能触发 brk int* big = new int[100000]; // 大内存,可能触发 mmap ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值