内存分配
ISO C指定了三个函数用于内存分配:
- malloc,它分配指定字节数的内存空间。被分配的内存空间初始值是随机。
- calloc,它分配指定对象数的内存空间。空间被初始化成0。
- realloc,它可以增加或减少之前分配空间的大小。当空间增长,它会把之前分配的空间移动到拥有大的地方再加上增长的空间做为结尾。同时,新增加空间的初始值是不确定的。
#include <stdlib.h>
void *malloc(size_t size);
void *calloc(size_t nobj, size_t size);
void *realloc(void *ptr, size_t newsize);
上面三个函数返回的指针确保拥有合适的对齐,可以存储任意类型的对象。例如,多数系统要求double类型数据要存储在内存的位置是8的倍数的位置,而通过这三个函数返回的指针已经是对齐的。
void free(void *ptr);
函数free可以释放ptr指向的内存空间,被释放的内存会放到一个可用内存池中,用于以后内存分配。
realloc函数可以让我们增加或减少之前分配空间的大小(多数用来增加)。例如,我们分配了一段空间可以在运行时存储512个元素,但在实际使用过程中发现不够,这时就可以使用realloc重新分配空间。如果在该存储区后有足够的空间可供扩充,则可在原存储区后面增加空间,无需移动数据,并返回原来的指针值。如果在原存储区后没有足够的空间,则realloc分配一个足够大的存储区,将现有数据移动到新分配的地址,然后释放原有地址空间,并返回新地址的指针。
注:如果realloc的参数ptr是null,那么它的行为和malloc函数一样。
分配内存的例程通常是用sbrk系统调用实现。这个系统调用扩展(或缩小)了进程的堆。
虽然sbrk可以扩展或减少进程的内存,但多数malloc和free的版本都不能减少它们的内存。free释放内存后可以再次使用它们,但是被free的内存没有返回到内核,而是留在malloc池中。
多数的实现会分配比需求多一点的内存,用这些额外的内存记录分配块的大小、 指向下一个块的指针,等等。这意味着在分配块中超量写入会覆盖下一个块的保存信息。这种类型的错误都是灾难性的,而且很难被发现,因为这种错误都是在程序运行很久以后才会出现。如果在当前分配块的开头之前进行覆盖写入,也会覆盖当前块的保存信息。
覆盖动态分配内存块的开头之前或结尾之后的信息不仅仅破坏了内部保存信息。一个动态分配的内存块之前和之后能被其它动太分配的对象隐式使用。这些对象很有可能和破坏它们的代码无关,这使得找到破坏源非常困难。
其它非常致命的错误包括使用free释放一个已经释放过的内存块、使用free释放一个不是使用alloc函数获得的内存块。如果进程设用malloc后忘记使用free释放内存,那么进程的内存会持续增长,这被称作——内存泄露。当超出页面文件时效率会降低。
因为内存分配的错误很难跟踪,一些系统提供了额外的版本,每次执行内存分配或释放内存时,都会进行检查。
其它的内存分配函数
libmalloc
基于SVR4的系统,如,Solaris,包含libmalloc库,它提供了一组匹配ISO C的内存分配函数。libmalloc库包含mallopt,一个允许进程设置可以控制存储分配器操作的变量。一个函数调用mallinfo可以提供内存分配器的状态。
vmalloc
Vo描述了一种内存分配器,它允许进程分配内存时,对不同区域的内存使用不同的技术。除了vmalloc特有的函数,该库同样提供了ISO C内存分配功能。
quick-fit
从历史上看,标准malloc算法要么使用best-fit策略,要么使用first-fit策略。quick-fit是比前两者都快,但是会用更多的内存。这个算法是基于将内存分割成多个不同大小的缓存块并维护不使用的缓存。
alloca函数
另外一个值得一提的函数——alloca。alloca使用方法和malloc一样;但是它分配的内存并不是从堆中分配,而是在栈中分配。它的优点是不用手动的释放内存,当程序返回时会自动释放分配的内存。缺点是很多系统不支持这个函数。
PS:LINUX支持alloca