在c语言的编写过程中,为了使程序有更好的移植性,也为了使程序编写起来更加方便,当需要额外虚拟内存时,会使用动态内存分配器(dynamic memory allocator)。
动态内存分配器维护着进程的虚拟内存区域:堆区。
分配器将堆视为一组大小不同的块(block)的集合来维护,每个块就是一个连续的虚拟内存片(chunk)。
块的状态有两种,详细介绍如下:
虚拟内存片状态 | 描述 |
---|---|
已分配 | 已分配的块显示地保留被应用程序使用。已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显示执行的,要么是内存分配器自身隐式执行 |
空闲 | 空闲块保持空闲,直到它显式地被应用程序所分配; |
分配器分类如下:
分配器分类 | 具体描述 |
---|---|
显示分配器(explicit allocator) | 要求应用显示地释放任何已分配的块。例如C语言中的malloc |
隐式分配器(implicit allocator) | 隐式分配器也叫做垃圾收集器,要求分配器检测一个已分配块何时不再被程序所使用,那么就释放这个块,释放过程就叫做垃圾收集 |
C标准库提供了一个称为malloc程序包的显式分配器。程序通过调用malloc函数来从堆中分配块。
malloc不初始化它申请的内存空间。
calloc函数是一个基于malloc的函数,作用和malloc一样,只不过它将分配的内存初始化为0。
realloc函数可以改变一个已分配块的大小。
程序是通过调用free函数来释放已分配的堆块。
//malloc函数返回指向size字节内存块的指针
//(在32bit中malloc返回的块的地址总数是8的倍数,在64bit中,是16的倍数)
void *malloc(size_t size);
//ptr必须指向一个已经已分配块的起始位置,如果不是,那么free的行为就是未定义的。
void free(*ptr);
在这里必须要指出free函数的一个缺陷,free函数的返回值是void,如果出现了错误我们也无从得知。
如果malloc申请内存失败,它将返回NULL
假设:malloc返回的块是8Byte双字边界对齐的。一个方框表示一个字。阴影部分表示已分配块。堆地址从左往右增加。
详细过程:
初始状态:
2. int* p2 =(int*)malloc(sizeof(int) * 5);程序请求5Byte的块。但是malloc从空闲块的前部切出一个6Byte的块,并返回一个指向这个块的第一个Byte的指针。malloc多分配一个额外的字,是为了保持空闲块是双字边界对齐的。
3. int* p3 =(int*)malloc(sizeof(int) * 6);程序请求6Byte的块。malloc从空闲块的前部切出一个6Byte的块,并返回一个指向这个块的第一个Byte的指针。
4. free(p2);程序释放②中申请的6Byte块。在调用free之后,指针p2仍然指向被释放的内存块,所以应该设置p2=NULL。
5.int* p4 =(int*)malloc(sizeof(int) * 2);程序请求2Byte的块。malloc从空闲块的前部(在前一步中被释放了的内存块)切出一个2Byte的块,并返回一个指向这个块的第一个Byte的指针。
在程序编写过程中,为了存放某些固定大小的数据时,我们会使用定长数组等。但是在某些情况下,对于某些数据的大小我们是并不知道的,假如我们还使用定长大小来设置,就会出现如下两种情况:①长度过小导致数据存储不下;②长度过小导致内存浪费。而动态内存分配是根据数据大小分配刚刚好合适的内存空间。
#include
#include
int main()
{
int* array;
int n = 0;//数据大小
int i = 0;
scanf("%d", &n);//输入数据大小
array = (int*)malloc(sizeof(int) * n);
//循环为每个元素赋值
for (i = 0; i < n; i++)
{
scanf("%d", &array[i]);
}
free(array);//释放array内存空间
array = NULL;
return 0;
}
要想使用malloc,calloc,realloc,free须先引入stdlib.h头文件
malloc是向堆区申请一块指定大小的连续内存空间,成功的话返回空间的首地址(保存好malloc开辟的首地址,不要改变其首地址),失败的话返回一个空指针。
free用来释放内存空间(通过malloc,calloc,realloc获得的动态内存空间),只能释放一次(空指针可以被释放多次)。
分配成功后如果空间被free之后,任然能够通过指向改变其值,所以在free之后要紧接着置指针为NULL
calloc与malloc的区别是calloc申请空间后会把空间内容初始化为00000000
realloc在原有分配空间基础上增加或减少空间。
堆区:程序运行时可以在堆区动态地请求一定大小的内存,并在用完之后归还给堆区。
int main()
{
int *p = NULL;
int *p1 = NULL;
p = (int*)malloc(sizeof(int)*5);//开辟5个连续空间
p1 = (int*)malloc(sizeof(int)*5);
int *ip = p1;
if(NULL == p)//必须有
{
exit(1);//开辟空间失败,终止当前程序的执行,
}
for(int i=0;i<5;i++)
{
p[i]=i;
}
free(p);//释放p指向的空间,将其还给堆区,这是p还指向该空间。
p = NULL;//防止失效指针对内存造成伤害
//free和置空NULL这两步必须可少
free(ip);//也是一种释放空间的方式
p1 = NULL;
ip = NULL;
return 0;
}
②动态开辟二维数组(注意释放空间时的顺序)
int main()
{
int **s = NULL;//4行5列
s = (int**)malloc(sizeof(int*)*4);//s里面存放的时一级指针,所以s为二级指针
//4行
if(NULL == s)
{
exit(1);
}
for(int i = 0;i < 4;i++)
{
s[i]=(int*)malloc(sizeof(int)*5);//5列
}
for(int i=0;i<4;i++)
{
for(int j=0;j<5;j++)
{
s[i][j]=i+j;
printf("%d\t",s[i][j]);
}
printf("\n");
}
for(int i=0;i<4;i++)
{
free(s[i]); //先释放
s[i] = NULL;
}
free(s); //释放s指向的空间
s = NULL;
return 0;
}
realloc表示在原有的基础上面进行追加,扩充到n个某类型空间。
有三种扩充方案:
①后续未分配内存空间足够大,可以分配空间。
②后续未分配内存空间不足够大,不能分配空间,这时就会在新的地址分配空间,把原来空间释放掉。
③堆内存不足,扩展空间失败,realloc函数返回NULL,这种情况会导致数据丢失,内存泄漏。
int main()
{
int n = 5,m = 10;
int *p = NULL;
p=(int*)malloc(sizeof(int)*n);
if(NULL == p)
{
exit(1);
}
for(int i = 0;i < n;i++)
{
p[i] = i;
}
int *s=(int*)realloc(p,sizeof(int)*m);//防止扩充空间失败,出现数据丢失,内存泄漏等情况
//扩充后s指向的空间时m*(sizeof(int))==40个字节空间。
if(NULL != s)
{
p = s;
}
else
{
printf("追加空间失败\n");
}
free(p);
p = NULL;
free(s);
s = NULL;
return 0;
}