目录
前言
free函数
malloc函数
calloc函数
realloc函数
扩充
有时候我们需要的空间大小不确定,需要随着程序需要的空间而变化, 那以数组开辟的固定大小的空间就不适用了, 这时候我们就需要动态分配开辟空间了。当空间不够时就扩容。动态开辟是在堆区开辟一块连续可用空间,并返回这块空间的地址。有三种函数malloc, calloc和realloc。我们动态内存分配就在堆区开辟空间
上面的四个区只有堆区的空间是需要手动释放的
free函数是专门用来对动态开辟内存的回收和释放的。当我们不需要再使用动态开辟的空间时,一定要free释放空间,因为是在堆上开辟的空间,所以不会随着出了作用域而销毁,需要我们free释放,避免内存泄漏,并置空(将指针置为NULL),避免形成野指针。
内存释放是标记删除, 只会修改当前空间的所属状态,并不会清除空间内容。
当然内存泄漏也不是都有危害,但为了养成良好的代码习惯,动态开辟后一定要free
void free (void* ptr);
1.如果参数ptr指向的内存空间不是动态内存开辟的,那free函数的行为是未定义的,就会报错,所以free函数是针对动态开辟的空间
2.如果ptr是 NULL空指针,那么free函数什么都不做。
下面我们来实现一下。
动态内存分配需要调用头文件#include
void* malloc (size_t size);
malloc函数用来动态开辟的, size是开辟所需空间的大小,单位:字节。
并返回起始地址。返回类型是void*,表示我们可以开辟任意类型的空间,同时我们需要强制类型转换成我们开辟空间类型的指针,再用指针接收
例如我们开辟的是int型的空间,就需要先转换为(int*),再用int*的指针接收
开辟float型的空间,就需要先转换为(float*),用于float*的指针接收
int* p = (int*)malloc(sizeof(int) * 10);
重点:
1.malloc函数在开辟空间后需要判断开辟空间是否成功,若开辟成功会返回开辟好的空间的指针,开辟失败会返回空指针。
2.malloc函数并不会对开辟空间进行初始化,空间内容为随机数。
下面是关于malloc函数的例子,主函数如下:
主函数中第一段代码就是在堆上动态开辟10个int型大小的空间,通过int*型的指针p去维护这块空间,如果空间开辟失败就会返回NULL,所以我们需要判断是否开辟成功。
#include
#include
#include
#include
int main()
{
int* p = (int*)malloc(sizeof(int) * 10);
if (p == NULL)
{ //我们也可以直接eixt(-1)异常退出,或是return;
printf("%s\n", strerror(errno)); //strerror(errno)是用来判断开辟失败的错误原因
return -1; //error是错误码
} //需要调用最下面的两个头文件
for (int i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
printf("\n");
return 0;
}
判断p为NULL时,也可以直接打印开辟失败,不需要调用strerror函数,也不用包含那两个头文件
if (p == NULL)
{
printf("malloc failed\n");
exit(-1);
}
void* calloc (size_t num, size_t size);
calloc函数与malloc类似 ,calloc可以看作malloc+memset
参数num是开辟空间的元素个数,参数size是元素的大小,单位:字节。
函数的功能是为 num 个 size大小 的元素开辟一块空间,并且把空间的每个字节初始化为0。
calloc函数与malloc函数不同点在于:会将开辟的空间都初始化为0(按字节初始化为0)。
#include
#include
#include
#include
int main()
{
int* p = (int*)calloc(10, sizeof(int));
if (p == NULL)
{
printf("%s\n", strerror(errno));
return -1;
}
for (int i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
printf("\n");
return 0;
}
下面看运行结果都为0
realloc函数让动态内存管理变得更加灵活,空间不够时可以对动态开辟的空间扩容
void* realloc(void* ptr, size_t szie);
参数ptr为需要扩充动态内存分配的空间的地址
size是 调整之后新大小,返回参数为调整之后的内存起始位置。
realloc在调整内存空间的是存在两种情况:
1.原地扩容
在需要扩容的空间后有足够的空间进行扩容,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
2.异地扩容
原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小 的连续空间来使用。这样函数返回的是一个新的内存地址。
pc指向的空间的数据会迁移到ptr指向的空间,此时不需要将pc的空间free,系统会自动释放。
下图通过两行代码了解什么是异地扩容,可以看到p1和p2的地址发生了变化
p1的值是0x006b56b8 p2的值是0x006bf4d0,所以扩容后的空间地址与原空间地址不同,这就是异地扩容
当然扩容失败就会返回NULL,异常退出了
下面是malloc和realloc的联合使用,我们扩容后要将扩容后的空间再次交给原指针去维护
所以将p=ptr
#include
#include
int main()
{
int* p =(int*) malloc(sizeof(int)*10);
if (p == NULL)
{
printf("malloc faied\n");
return -1;
}
int* ptr = (int*)realloc(p, sizeof(int) * 20);
if (ptr == NULL)
return -1;
p = ptr;
for (int j = 0; j < 20; j++)
{
printf("%d ", p[j]);
}
printf("\n");
return 0;
}
运行结果可以看到都是随机数,所以realloc函数也不会初始化
可以看出realloc与malloc相似,都不会初始化。同时这也是realloc和malloc的一个特性,
当要扩容的对象为空时,realloc可以当作malloc函数使用。
我们一般动态开辟的空间都是结构体,下面简单介绍一下开辟结构体类型的空间
我们首先定义了一个结构体类型,里面定义的是int型的数据data,还有一个next型的指针,存放下一节点的地址,这就是数据结构的单链表结构,不了解的小伙伴可以简单看一下
pc指向开辟的一个节点用,p指向开辟的另一个节点用,将pc指向的结构体中的next保存p指向节点的地址
#include
#include
typedef struct QueueNode
{
DataType data;
struct QueueNode* next;
}QueueNode;
int main()
{
QueueNode* pc = (QueueNode*)malloc(sizeof(QueueNode));
QueueNode* p = (QueueNode*)malloc(sizeof(QueueNode));
if (pc == NULL && pc == NULL)
{
printf("malloc failde\n");
exit(-1);
}
pc->data = 10;
pc->next = p;
p->data = 20;
p->next = NULL;
printf("%d %d\n", pc->data, p->data);
printf("%p %p %p\n", pc->next, p, p->next);
free(pc);
free(p);
pc = p = NULL;
return 0;
}
重点:动态开辟使用完后,一定要记得free,置空。
了解完上面的内容,是不是对动态分配有了更深的理解。不是看到希望才去坚持,而是坚持了才看到希望,敬每一位努力奋斗的你和学习编程的你。希望我的文章能对你有所帮助。欢迎点赞 ,评论,关注,⭐️收藏