前言
有时候我们需要的空间大小不确定,需要随着程序需要的空间而变化, 那以数组开辟的固定大小的空间就不适用了, 这时候我们就需要动态分配开辟空间了。当空间不够时就扩容。动态开辟是在堆区开辟一块连续可用空间,并返回这块空间的地址。有三种函数malloc, calloc和realloc。我们动态内存分配就在堆区开辟空间
上面的四个区只有堆区的空间是需要手动释放的
free函数
free函数是专门用来对动态开辟内存的回收和释放的。当我们不需要再使用动态开辟的空间时,一定要free释放空间,因为是在堆上开辟的空间,所以不会随着出了作用域而销毁,需要我们free释放,避免内存泄漏,并置空(将指针置为NULL),避免形成野指针。
内存释放是标记删除, 只会修改当前空间的所属状态,并不会清除空间内容。
当然内存泄漏也不是都有危害,但为了养成良好的代码习惯,动态开辟后一定要free
void free (void* ptr);
1.如果参数ptr指向的内存空间不是动态内存开辟的,那free函数的行为是未定义的,就会报错,所以free函数是针对动态开辟的空间
2.如果ptr是 NULL空指针,那么free函数什么都不做。
下面我们来实现一下。
动态内存分配需要调用头文件#include
malloc函数
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); }
calloc函数
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函数
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,置空。
了解完上面的内容,是不是对动态分配有了更深的理解。
malloc/calloc/realloc区别总结
相同点:
1.都是从堆上申请空间
2.都需要对返回值判空
3.都需要用户free释放
4.返回值类型相同(void*)
5.都需要类型转化
6.底层实现上是一样的,都需要开辟多余的空间,用来维护申请的空间
可以输入以下代码观测内存:
#include#include int main(){ int *p= (int *)malloc(sizeof(int )*10); return 0; }
不同点:
1.函数名字不同和参数类型不同。
2.calloc会对申请空间初始化,并且初始化为0,而其他两个不会。
3.malloc申请的空间必须使用memset初始化
4.realloc是对已经存在的空间进行调整,当第一个参数传入NULL的时候和malloc一样
总结
到此这篇关于C语言中动态内存分配malloc、calloc和realloc函数解析的文章就介绍到这了,更多相关C语言动态内存分配函数内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!