来自《Linux/UNIX系统编程手册》内存分配一章
Linux的进程用户空间内存布局(实际上是简化版)
主要关注这五个段(更准确的说是“区(section)”):
- 文本段:存放程序代码,只读。因为代码可以被多个进程共享,所以在物理空间实际上只有一份
- 初始化数据段:存放初始化的全局变量和静态变量,程序加载时从可执行文件读取
- 未初始化数据段:一般称作BSS段,存放未初始化的全局变量和静态变量,在程序执行前全部被初始化为0,这样在可执行文件中就不用记录数据的值,只要给一个段的位置和长度,直到加载时才分配空间
- 栈:由栈帧组成,每个函数被调用时会分配一个栈帧,存放局部变量、实参、返回值等等,向地址减小方向增长
- 堆:用于动态分配内存的区域,堆顶称为program break,从BSS段尾部向地址增大方向增长
应用程序分配内存可以从栈或者堆上分配
从堆上分配
调整堆的大小也就是调整program break的位置,Linux提供了两个系统调用,brk()和sbrk()
- int brk(void *end_data_segment)将program break调整为参数end_data_segment指定位置,由于内存分配按照页为单位,所以实际上内部会调整program break到页的边界,注意brk可以让program break上升也可下降
- void *sbrk(intptr_t increment)将program break加上increment,这是个整数类型即program break可以上升也可以下降,sbrk(0)将返回当前program break位置
一般来说我们习惯使用malloc()和free()来分配和释放动态内存,他们的实现实际上使用了brk()和sbrk()两个调用 - void *malloc(size_t size)分配size字节大小的内存,一般来说实际分配时会内存对齐
- void free(void *ptr)释放内存
free()一般并不会降低program break的位置,而是将内存添加到空闲内存列表(后面会展示),只有当在堆的顶部释放时,并且当堆顶连续空闲空间足够大时才会调用sbrk()调整program break
malloc()和free()的实现
当调用malloc()时扫描空闲内存列表,然后找一块大小大于等于要求的空闲内存(这里看具体的实现使用什么策略比如best-fit或者first-fit等等),如果大小相等就返回,如果大小超过要求就切割出来一部分,然后将剩余部分留在空闲内存列表中。如果没有满足要求的空闲内存,才会用sbrk()分配更多内存。
调用free()时回收内存并放入空闲内存列表。当malloc分配内存时实际上会额外分配几个字节来记录分配的内存大小,如下图:
所以free在回收时就能知道内存块的大小,将内存块放进空闲内存列表时,会利用内存本身的空间来存放链表指针,将所有空闲内存块串起来:
那么实际上堆中的内存应该是这样管理的:
realloc()调整分配内存
通常当我们需要增大已分配的内存空间时,使用void *realloc(void *ptr, size_t size)将ptr位置的内存变为size字节大小。
增大已经分配的内存,realloc()首先会尝试合并紧随已分配内存后的空闲内存,如果说已分配内存在堆顶部那么会扩展堆空间;如果找不到这样的空闲内存或者空闲内存不够大,那么需要重新分配一块大内存,并将原来的部分复制过去,因此调用realloc后原指针可能失效。
在栈上分配内存
使用void *alloc(size_t size)可以扩展当前函数栈帧的大小,这样分配的内存是临时的,所以不能用free()来释放,相对于malloc它是直接调整栈指针所以速度快,而且在函数返回时分配的内存就会被销毁,无需自己释放,在部分场合可以发挥较好的效果。