在很多系统当中需要使用到一些先进的数据结构,他们的大小或者形状是随着程序的推进而演化,这个时候在程序开始之初我们并没有能力确认到底给他分配多少空间。所以这一章我们将讨论用于在堆和栈上分配内存的功能函数。
7.1 在堆上分配内存
一个进程可以通过增加堆的尺寸来分配内存。堆是一个用来存放动态分配的变量的空间,它位于未初始化数据段(bss)之后,它的顶叫做program break,这个地方会根据内存的分配和释放而变化。一般来讲C语言堆内存的分配一般会使用malloc(),它是基于brk()和sbrk()所实现的。
7.1.1 调整Program Break: brk() 和sbrk()
调整heap区域的大小其实非常简单,也就是让内核调整Program Break的位置。在最刚开始什么都没有分配的时候,Program break就是紧挨着Unitialized data(bss)区域向上。当Program Break被增加了之后,程序就可以自由访问它之下在Heap范围内的任何被新分配的区域,但是这时候并还没有物理内存的页被分配。Kernel会在该进程第一次尝试进入该物理页的时候分配给新的物理内存页。
通常来说,UNIX系统提供了两种方法来改变program break的位置,在Linux中当然也是两种,他们叫做brk()和sbrk()。一般来讲我们在真正的应用层程序中不太可能直接使用brk()以及sbrk()(一般都直接使用malloc和free,因为他们已经将底层方法进行抽象),但是对于他们的理解却是可以帮助我们理解到底内存分配是如何工作的。
基本使用方法:
#include <unistd.h>
int brk(void *end_data_segment);
//Returns 0 on success, or –1 on error
void *sbrk(intptr_t increment);
//Returns previous program break on success, or (void *) –1 on error
brk()可以直接将program break设置在end_data_segment所指定的位置上。因为虚拟内存都是以页的形式分配,所以end_data_segment一定要被进到更大的整数页数上,比如现在是需要2.2页,则我们需要end_data_segment是3。
如果我们尝试将end_data_segment设置为program break初始值之下的话,则会触发segmentation fault。对于program break生长的限制取决于多个因素:进程资源对数据段的限制(RLIMIT_DATA在36.3中)还有就是内存映射的位置,共享内存段以及共享库等等。
对sbrk()的调用会通过调整往上的相对增加值来实现对program break的改变。成功之后,sbrk()会返回program break之前的地址。这里面可以这样子理解, 当program break增加之后,我们就会得到新被分配内存块的起始地址,并且我们知道它有多大。
sbrk(0)的调用会返回目前program break的位置,并不会改变任何东西。使用sbrk(0)可以帮助我们追踪heap目前的大小,可以用来监控内存分配package的行为。
7.1.2 在Heap上分配内存:malloc()和free()
一般来讲,C程序会使用malloc程序来分配或者取消分配heap上的内存空间。这些函数相比于brk()和sbrk()提供了众多的好处:
- 作为C语言的标准化的一部分
- 更易于在线程化的程序当中使用
- 提供一个简单的接口而允许内存在小的单元中被分配
- 允许我们任意取消对内存块的分配,并且这些信息会维持在一个free list上以方便未来可以对该空内存部分的循环使用。
malloc()会分配size大小的内容并返回起始位置的指针大小,并且被分配的内存并没有被初始化。
#include <stdlib.h>
void *malloc(size_t size);
因为malloc()返回的是void*类型,所以我们可以将该值赋予指向任何类型的指针。由malloc()返回的内存块总是要保持8位或者16位的整数倍的大小(俗称alignment)。
如果内存不能够被分配(可能因为我们到达了program break的限制),那么malloc()会返回NULL指针和errno来表示错误内容。尽管分配内存错误的可能性非常小,但我们仍然需要检查他是否出现错误。
free()函数用来释放被malloc()返回指针指向的程序块。
#include <stdlib.h>
void free(void *ptr);
一般来讲,free()并不会降低program break,但是它会将被释放的内存空间的地址放在一个free blocks的列表当中,之后可以被malloc()所循环利用。这是因为以下一些原因:
- 被释放的内存块一般来讲都位于现已利用的堆的中间,所以降低program break是不可能的。
- 它可以减少对sbrk()的调用,因为系统调用的开销相对比较大
- 在很多例子当中,降低program break并不会帮助分配大内存给程序。因为他们经常会被分配内存或者重复性的释放并分配内存,而不是释放他们所有并继续运行一段时间。
如果给free()一个NULL指针作为参数,那么这个调用就会什么都不做。随便丢给free()一个指针,比如对同一个指针地址调用两次free(),会产生一个不可预知的错误。
例程
下列程序可以用来解释free()对program break的效果-程序分配多个内存块并释放他们中的一些。
命令行参数的前两个表示想要分配的内存块的数量和大小,第三个参数表示当释放内存块的时候