数组的元素存储在内存中连续的位置上。当一个数组被声明时,所需的内存在编译时就被分配。当然也可以使用动态内存分配使得在运行时为它分配内存。
1. 为什么使用动态内存分配?
声明数组的时候,必须使用一个编译时候的常量来指定数组的长度。数组的长度往往在运行时候才知道,因为所需的内存空间取决于用户的输入数据。
2. malloc和free
C函数库提供了malloc和free两个函数,分别用于执行动态内存的创建和释放。这些函数维护了一个可用的内存池。这两个函数的原型如下所示,都在头文件stdlib.h中声明:
void *malloc(size_t size);
void free(void *pointer);
malloc的参数就是需要分配的内存字节数。如果内存池满足申请的需求就会返回一个指向被分配的内存块起始位置的指针。如果不满足,就会返回NULL指针。
malloc在请求内存的时候不需要知道存储的是整数、浮点数、结构还是数组,因为返回的是一个void *的指针,标准表示一个void *类型的指针可以转换为其他任何类型的指针。
3. calloc和realloc
另外两个分配内存的函数calloc和realloc。它们的原型如下所示:
void *calloc(size_t num_elements,size_t element_size);
void realloc(void *ptr,size_t new_size);
calloc函数也用于分配内存。malloc和calloc的主要区别是:后者在返回指向内存的指针之前把它初始化为0。这个初始化常常能带来方便。如果只是把值存储到数组中,那么这个初始化值纯属就是浪费时间。
它们还有另外一些小区别:请求内存数量的方式不同。calloc的参数包括所需元素的数量和每个元素的字节数。根据这些值可以计算出总共需要分配的内存。
realloc函数用于修改一个原先已经分配的内存块的大小。使用这个函数,可以使得一块内存扩大或缩小。如果扩大内存,原先的内容依然保留,新增加的内存添加到原先内存块的后面,新内存并未以任何方法进行初始化。如果用于缩小一个内存块,该内存尾部的部分内存便被拿掉,剩余部分的内容依然保留。
如果原先的内存块无法改变大小,realloc将分配另一块正确大小的内存,并把原来的内存的内容复制到新的块上。
如果realloc函数的一个参数是NULL,那么它的行为就和malloc一模一样。
4. 使用动态分配的内存
int *pi;
pi = malloc(100);
if(pi == NULL)
{
printf("out of memory!\n");
exit(1);
}
符号NULL定义于stdio.h,实际上是一个字面值常量0。如果内存分配成功的话,就会拥有一个指向100字节的指针。在整型为4字节的机器上,这块内存将被当作25个整型元素的数组。
还可以这样定义:
pi = malloc(25*sizeof(int));
这个写法更好,因为这个是可移植的。即使在整数长度不同的机器上,也能获得正确的结果。
使用这块内存,可以通过使用间接访问和指针运算来访问数组的不同整数的位置。
int *pi2,i;
pi2 = pi;
for(i=0;i<25;i++)
{
*pi2++=0;
}
5. 常见的动态内存错误
在使用动态内存分配的过程中,常常会出现许多错误。这些错误包括:
1. 对NULL指针进行解引用操作。
2. 对分配的内存进行操作时越过边界。
3. 释放并非动态分配的内存。
4. 试图释放一块动态分配的内存的一部分。以及一块动态内存被释放之后被继续使用。
动态分配最常见的错误就是:
1. 忘记检查所请求的内存是否分配成功。
2. 操作内存时超出分配内存的边界。
a. 被访问的内存可能保存了其他变量的值。对它进行修改将破坏那个变量,修改那个变量将破坏存储在那里的值。这种类型的bug非常难以发现。
b. 在malloc和free的有些实现中,以链表的形式维护可用的内存池。对分配的内存之外的区域进行访问可能破坏这个链表,这有可能产生异常,从而终止程序。
alloc.h
/*
定义一个不易发生错误的内存分配器
*/
#include <stdlib.h>
#define malloc
#define MALLOC(num,type)(type *)alloc((num)*sizeof(type))
extern void *alloc(size_t size)
程序a:错误检查分配器:接口
/*定义一个不易发生错误的内存分配器*/
#include <stdio.h>
#include "alloc.h"
#undef malloc
void *alloc(size_t size)
{
void *new_mem;
//请求所需的内存,并检查确实分配成功
new_mem = malloc(size);
if(new_mem == NULL)
{
printf("out of memory!\n");
exit(1);
}
return new_mem;
}
程序b:程序检查分配器:实现
/*一个使用很少引起错误的内存分配器的程序*/
#include "alloc.h"
void function()
{
int *new_memory;
/*获得一串整型数的空间*/
new_memory = MALLOC(25,int);
}
最后警告:不要访问已经被free函数释放了的内存。内存要及时进行释放,释放一块内存的一部分是不被允许的,动态分配的内存必须整快释放。
内存泄漏
当动态分配的内存不再需要时候,应该被释放,以后可以被重新分配使用。分配内存但在使用完毕后不释放将引起内存泄露(memory leak)。在那些所有执行程序共享一个通用内存池的操作系统中,内存泄露将一点点地榨干可用内存,最终使其一无所有。要摆脱这个困境,只有重启系统。