1.为什么要动态内存分配:
以前我们开辟内存空间的方式:
int n=10;//在栈上开辟4个字节大小的空间
int arr[10]={0};//在栈上开辟40个字节大小的空间
上述开辟空间的方式有两个特点:
1.所开辟的空间大小是固定的
2.数组在开辟内存空间时,必须要指明数组长度,数组空间一旦确定便不能改变大小。
但有时候对空间的需求,并不仅仅是上述的情况。有时我们需要的空间的大小要在程序运行时才知道,所以数组编译时所开辟的空间的方式就不能满足。
C语言引入动态内存分配,让程序员自己可以申请和释放空间,更加灵活。
2.malloc函数:
函数原型:
void* malloc(size_t size);// size单位是字节
malloc在内存中申请一块size大小的内存空间,并返回指向这块空间的指针。
1.如果malloc成功开辟了一块内存空间,那就会返回指向这块空间的起始地址。
2.如果malloc开辟空间失败,就会返回NULL,所以我们要对malloc返回要做检查,避免成为野指针。
#include<stdio.h>
int main()
{
int* p=(int*)malloc(5*sizeof(int));//开辟一块20字节的内存,并用来存放整型数据
if(p!=NULL)//要进行检查,不为NULL才使用
{
*p=20;
}
}
3.因为返回类型是void*,所以malloc并不知道所开辟空间的类型,使用的时候需要使用者自己决定。
4.如果size为0,malloc的标准就是未定义的,要取决于编译器。
3.calloc函数:
calloc函数也是用来动态内存分配的,calloc原型:
void* calloc(size_t n,size_t size);
1.n代表个数,size代表字节大小,意思在内存中分配n个长度为size的连续内存空间。
2.如果calloc成功开辟了一块内存空间,就会返回指向这一块空间的起始地址。
3.开辟失败,就会返回NULL,所以最好也要进行检查后才使用。
4.calloc还会将所开辟的空间全部初始化为0,malloc开辟的内容是随机的。可以根据自身需求进行选择使用。
#include<stdio.h>
int main()
{
int* p = (int*)calloc(5, sizeof(int));//p是calloc开辟的
int* ptr = (int*)malloc(sizeof(int) * 5);//ptr是malloc开辟的
if (p != NULL&&ptr!=NULL)
{
printf("calloc:");
for (int i = 0; i <= 4; i++)
{
printf("%d ", p[i]);
}
printf("\nmalloc:");
for (int i = 0; i <= 4; i++)
{
printf("%d ", ptr[i]);
}
return 0;
}
else
{
perror("p");
return 1;
}
}
上面代码,calloc和malloc开辟的空间大小一样,且都是整型类型。
运行结果:
所以当我们需要对内存空间内容初始化时就可以用calloc函数。
4.free函数:
我们使用malloc,calloc和realloc这些函数开辟一块内存空间,当我们使用完这块内存空间并不再使用时,我们要及时将这块空间归还操作系统,以免酿下大错。而free就是专门做动态内存的释放和回收的。
free原型:
void free(void* ptr);
1.free无返回值,ptr是调用malloc,calloc和realloc函数开辟内存空间时返回的起始地址(指针)。
2.free只能释放动态开辟的空间,如果free释放的空间是非动态开辟,那free函数的行为是未定义的。
3.如果ptr是NULL,则free函数什么都不做。
#include<stdio.h>
int main()
{
int* p=(int*)malloc(5*sizeof(int));//开辟一块20字节的内存,并用来存放整型数据
if(p!=NULL)//要进行检查,不为NULL才使用
{
*p=20;
free(p);//使用完动态内存后,释放内存
p=NUll;
}
}
在上面代码中,在释放后动态内存空间后,此时p中的地址还是指向开辟内存空间的地址,但这个内存空间不属于p了,已经归还给操作系统了,所以如果再使用p就会形成野指针,所以最好就把p置为NULL。
5.realloc函数:
有了realloc函数,能使动态内存管理更为灵活。有时候我们发现我们开辟的动态内存空间偏大或偏小,为了合理使用内存,就要对内存大小进行调整。realloc函数就可以对动态开辟的内存大小进行灵活调节(reallo可以扩大或缩小内存空间)。
realloc原型:
void* realloc(void* ptr,size_t size);
void*ptr:需要调正内存大小的地址。
size_t size:需要开辟内存的大小(单位字节)
返回值:调整之后的内存空间的起始地址。
在realloc函数在调节动态内存空间时会有两种情况:
#include<stdio.h>
int main()
{
int* p=(int*)malloc(sizeof(int)*5);
if(p==NULL)//检查空间是否开辟成功
{
return 1;
}
else
{
*p=20;//操作空间
}
int* ptr=(int*)realloc(p,40);//扩大内存空间
if(ptr!=NULL)
{
p=ptr;//把扩大后的地址ptr交给p来管理
}
return 0;
}
上面这个代码,p开辟了一个20字节的内存,我们想将内存空间扩大为40字节
第一种情况:如图,realloc调节内存时,如果初始内存(20字节)加上后面的未被分配内存大于或等于目标内存(40字节)时,要扩展的内存就加在原始内存后面,返回的起始地址依然是malloc开辟空间返回的起始地址。
第二种情况:如图,realloc在调节内存时,后面的内存不够时,此时realloc就会在堆上重新找一个适合大小的连续空间来使用,因此就会返回一个新的起始地址。会经历以下过程:
(1)在堆区上找到新的内存空间,并且新的内存空间是连续且满足调节后的内存大小的。
(2)将原来空间的数据拷贝一份到新的空间来。
(3)释放旧空间
(4)返回新的内存的起始地址。
realloc可以进行完成和malloc一样的功能:
realloc(NULL,20)=malloc(20);