今天所介绍的动态内存分配是在堆区上实现的
目录
为什么存在动态内存分配?
我们知道
int a = 10; //在栈区开辟了4个字节大小的空间
char arr[10] = { 0 }; //在栈区开辟了10个字节的连续空间
这种开辟空间的方式有两个特点
1.空间开辟的大小是固定的
2.数组在申明的时候,必须指定数组的长度, 它所需要的内存在编译时分配
但是有些时候,我们所需要的内存空间大小只有在运行时才能知道,这时数组在编译时开辟内存空间的方式就不一定能满足需求了,这时候就需要动态内存开辟
动态内存函数的介绍
malloc和free
malloc和free的函数原型
void* malloc(size_t size)
//size_t size代表需要开辟的字节数
void* free(void* ptr)
//void* ptr代表要被回收的空间的地址
void* malloc(size_t size)
//size_t size代表需要开辟的字节数
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针
如果开辟成功,则返回一个指向开辟好空间的指针
如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查
void* free(void* ptr)
//void* ptr代表要被回收的空间的地址
这个函数专门用来做动态内存的释放和回收
ptr指向的空间必须是动态开辟的
如果ptr指向NULL,则函数什么事也不做
malloc和free函数都声明在<stdlib.h>头文件中
举一个例子
int arr[10] ={ 0 }; //在栈区开辟40个字节的连续空间
malloc(10*sizeof(int)); //在堆区开辟40个字节的空间
由malloc的函数原型可知,这个函数的返回类型是void*类型,因此可以用一个void*类型的指针变量来接收
void* p = malloc(10*sizeof(int)); //在堆区开辟40个字节的空间
但是往往很少这样写,因为这并不是我们想要的。试想一下,我们能对指针变量p进行解引用操作吗?
void* p = malloc(10*sizeof(int));
*p; //能不能这样写?
答案是否定的,这样做是非法的间接寻址 ,我们再来看一下这两句代码
int arr[10] ={ 0 }; //在栈区开辟40个字节的连续空间
malloc(10*sizeof(int)); //在堆区开辟40个字节的空间
我想用动态内服开辟的方式存10个整型变量,但因为malloc的返回类型是void*,因此我们要强制类型转化成int*类型
int* p = (int*)malloc(10*sizeof(int)); //强制类型转化成int*类型
if(p == NULL) //判断开辟是否成功,检查返回值
{
perror("main"); //会打印“main:错误信息”
return 0;
}
在这之后,我们就可以对开辟的内存进行操作
操作完之后,使用free()回收空间
int* p = (int*)malloc(10*sizeof(int));
if(p == NULL)
{
perror("main");
return 0;
}
······ //操作
//操作完毕后,回收空间
free(p);
这样过后,free把p指向的空间还给了操作系统,但是并没有把指针变量p置成空指针,p中仍然存放连续空间的起始地址,如果此时又使用p来访问之前的空间,就是非法访问,因此在回收空间后,我们要手动把指针变量置成空指针
int* p = (int*)malloc(10*sizeof(int));
if(p == NULL)
{
perror("main");
return 0;
}
······ //操作
//操作完毕后,回收空间
free(p);
p = NULL; //手动置成空指针
calloc
calloc的函数原型
void* calloc(size_t num,size_t size)
//num代表需要开辟的元素个数,size代表开辟一个元素需要多少字节
举一个例子
calloc(10,sizeof(int);
//开辟10个4个字节的连续内存空间,并把他们初始化为0
那malloc和calloc的异同点都有什么呢?
相同点
1.他俩都能开辟一块内存空间
2.都需要检查是否开辟成功,检查返回值
3.使用后都需要free来回收空间
4.想要使用都需要引头文件<stdlib.h>
不同点
1.函数原型不同
2.calloc开辟的内存空间会被初始化成0,而malloc不会被初始化
realloc
realloc的作用
1.realloc函数的出现使得动态内存管理更加灵活
2.有时我们会觉得申请的空间太小了,又有时候会觉得申请的空间过大,我们这时就要对内存的大小做出灵活的调整,而realloc函数就可以做到对动态开辟内存大小的调整
realloc的函数原型
void* realloc(void* ptr, size_t size)
//ptr是要调整的内存地址
//size是调整之后的新大小,注意不是增加的大小,而是增加后的总大小
//返回值为调整之后的内存起始地址
举个例子
int main()
{
int* p = (int*)malloc(10*sizeof(int));
if (p == NULL) //检查是否开辟成功
{
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = 5;
}
//假设现在还想再开辟10个元素的整型空间
//就是需要p指向更大的空间,需要20个int的空间,用realloc来调整空间
return 0;
}
怎样调整呢?
int main()
{
int* p = (int*)malloc(10*sizeof(int));
if (p == NULL) //检查是否开辟成功
{
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = 5;
}
//正确的操作
int* ptr = (int*)realloc(p, 20*sizeof(int));
if(ptr != NULL) //如果开辟失败会返回空指针NULL,因此要检查返回值
{
p = ptr;
}
return 0;
}
那么问题来了,我们为什么要向上面这种方法操作呢?
p = (int*)realloc(p, sizeof(int))
//用自己来接收自己不是更简单吗?为什么要绕一个弯子
想要弄明白这个问题,我们先来说一下realloc在调整内存空间时的两种情况
realloc在调整内存空间时存在两种情况
情况一:原有空间之后有足够大的空间
情况二:原有空间之后没有足够大的空间
第一种情况,p的后面的空间比想要开辟的空间大,这是正常的情况,重点是第二种情况
第二种情况,p的后面的空间比想要开辟的空间小 ,接着会寻找一块有80字节的空间,会将p中的内容拷贝到ptr中。如果寻找不到这样一块80字节的空间,会返回空指针NULL
最后来解答一下上一节的最后一个问题,为什么不这样写?
p = (int*)realloc(p, sizeof(int))
答案是不能这样写,如果开辟失败,会返回一个空指针NULL,这样不仅开辟新内存失败,连原来的内容都被置成空指针NULL了,得不偿失,因此我们新定义一个指针变量来过渡一下,最后再检查一个返回值,就没有问题了 ,正确操作的方式如下
int main()
{
int* p = (int*)malloc(10*sizeof(int)); //开辟了一块40个字节的连续空间
if (p == NULL) //检查是否开辟成功
{
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = 5;
} //这时想要多开辟一块空间
//正确的操作
int* ptr = (int*)realloc(p, 20*sizeof(int));
if(ptr != NULL) //如果开辟失败会返回空指针NULL,因此要检查返回值
{
p = ptr;
}
return 0;
}
特别的
int* p = (int*)realloc(NULL, 80);
//与malloc(80)的功能一样
柔性数组
什么是柔性数组?
C99中,结构体的最后一个成员变量可以是一个未知大小的数组,这个数组就是柔性数组成员
struct S
{
int a;
int b[]; //柔性数组成员
};
或者
struct S
{
int a;
int b[0]; //柔性数组成员
};
柔性数组的特点
1.结构中的柔性数组成员的前面必须至少一个其他成员
2.sizeof返回的这种结构大小不包括柔性数组的内存
3.包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小
我们举一个例子来解析这些特点
struct S
{
int n;
int arr[]; //柔性数组成员
};
int main()
{
struct S s1 = { 0 };
printf("%d", sizeof(s1)); //结果是多少?
return 0;
}
上述代码柔性数组成员的前面有一个其他成员,柔性数组的第一个特点就是柔性数组成员前面必须至少一个其他成员
那么sizeof(s1)的结果是多少呢?
这就是柔性数组的第二个特点 :sizeof返回的结构体的大小不包括柔性数组的内存
最关键的是第三点,那么我们怎样正确开辟这块空间呢?
struct S
{
int n;
int arr[]; //柔性数组成员
};
int main()
{
//假设我想在数组中存储10个元素
struct S* ps = (struct S*)malloc(sizeof(struct S) + sizeof(10 * sizeof(int));
return 0;
}
我可以随意用realloc函数来调整数组的大小,这就体现了“柔性”
假如我们现在觉得10个元素不够用,要扩大到20个元素,完整代码如下
struct S
{
int n;
int arr[]; //柔性数组成员
};
int main()
{
//假设我想在数组中存储10个元素
struct S* ps = (struct S*)malloc(sizeof(struct S) + sizeof(10 * sizeof(int));
if(ps = NULL) //判断是否开辟成功,检查返回值
return 1;
//10个不够,要用20个
struct S* ptr = (struct S*)realloc(ps,sizeof(struct S) + sizeof(20 * sizeof(int));
//调整数组的大小
if(ptr == NULL) //判断是否修改成功·检查返回值
return 1;
else
{
ps = ptr;
}
······· //操作
//使用完毕后,要回收空间
free(ps);
ps =NULL //防止非法访问,把ps置成空指针
return 0;
}