1、为什么存在动态内存分配?
我们之前为数据开辟的空间都是在栈上开辟的,但是,在栈上开辟空间有几个缺点。
第一个缺点:开辟空间小,没办法使用大空间,当开辟大空间之后,系统会发生错误。
第二个缺点:开辟空间是固定的,然而有的时候我们需要的空间在程序运行的时候才能知道。
基于以上两个缺点,就出现了动态内存开辟的解决方法。
2、动态内存函数的介绍
(1)malloc和free
a、malloc
动态内存开辟函数:
void * malloc(size_t size);
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
●如果开辟成功,则返回一个指向开辟好空间的指针。
●如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
●返回值的类型是void*,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来确定。
●如果size为0,malloc的行为是标准是未定义的,取决于编译器。
b、free
free函数是专门用来做动态内存的释放和回收的,函数原型如下:
void free(void * ptr);
free函数用来释放动态开辟的内存。
●如果参数ptr指向的空间不是动态开辟的,那free函数的行为就是未定义的。
●如果参数ptr是NULL指针,则函数什么事都不做。
●free的时候,是将开辟空间内的数据变为随机值,并不是变为0。
注意:(1)malloc申请的空间在堆上申请。
(2)因为malloc是函数,所以堆空间只能在运行时确定。
(3)malloc在堆上申请空间,程序员申请,程序员释放。如果不释放,会有内存泄漏问题。程序退出,内存 泄漏问题随之消失。
(4)free时释放的是指针和内存块(堆)的关系。
(5)malloc的时候,并不是申请多少字节就开辟多少字节,开辟的字节是比程序员申请的字节要多的。多余的字节里面放的是元信息,比如开辟的时间、什么人开辟的等等一系列的信息。所以用malloc开辟空间的时候,开辟的越多越合适,毕竟无论你要开辟多少字节,都必须开辟一段字节用来保存元信息。free的时候释放的字节是全部空间,包括元信息所占的空间。
我们来举一个例子了解一下malloc和free的使用:
#include<stdio.h>
#include<stdlib.h>
int main(){
int num=0;
printf("请输入要开辟的空间大小:")
scanf("%d",&num);
char * ptr=NULL;
ptr=(char * )malloc(num*sizeof(char));
if(NULL!=ptr)//判断ptr指针是否为空
{
int i=0;
for(i=0;i<num;i++)
{
*(ptr+i)=0;
}
}
free(ptr);//释放ptr所指向的动态内存
ptr=NULL;//此时,ptr指向的是随机值,ptr就变成了野指针。这一步很有必要。
return 0;
}
2、calloc
calloc也是用来进行动态内存分配的,与malloc的区别是calloc会初始化。
原型如下:
void * calloc(size_t num,size_t size);
●函数的功能是为num个大小为size的元素开辟一块空间,并把空间的每个字节初始化为0。
●calloc也需要free释放。
3、realloc
在进行malloc动态内存开辟的时候,可能会发现开辟的空间多了或者是少了,那这个时候就需要用到realloc函数了。realloc函数就可以做到对动态开辟内存大小的调整。
函数原型如下:
void * realloc(void * ptr,size_t size);
●ptr是要调整的内存地址
●size调整之后新大小
●返回值为调整之后的内存起始位置
●这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
调整内存大小时分为两种,一种是调大,一种是调小。调小直接在开辟的内存上free即可,调大不行。
调大有两种情况:
情况1:原有空间之后有足够大的空间,直接向后扩容即可。
情况2:原有空间之后没有足够大的空间,则需要重新开辟空间。
在情况2的时候,就要注意一些问题,举个例子:
#include <stdio.h>
#include<stdlib.h>
int main()
{
int *ptr = malloc(100);
if(ptr != NULL)
{
//业务处理
}
else
{
exit(EXIT_FAILURE);
}
//扩展容量
//代码1
ptr = realloc(ptr, 1000);//这样可以吗?(如果申请失败会如何?)
//代码2
int*p = NULL;
p = realloc(ptr, 1000);
if(p != NULL)
{
ptr = p;
}
//业务处理
free(ptr);
return 0;
}
明显,代码1就不严谨,如果是情况2的话,开辟失败,依旧将开辟失败的地址赋给ptr,那么就没办法指向原来的数据了,得不偿失。代码2就是正确的,既保证了开辟失败之后,原始数据没有受影响,并且对开辟成功与否做了判断。