C语言中的动态内存分配
在很多场景,数据的大小是在运行时才能确定,编译阶段无法确定数据长度;或者有大块的数据需要存放,但是栈空间不够;或者某些参数需要实现跨函数传递;或者需要实现可变的数据结构。当面临这些问题时,我们往往想到动态分配内存。本文将主要讲在C语言中,动态内存分配的4个常用函数,以及使用注意事项。
一、为什么要用动态内存
栈内存存放局部变量,大小编译期确认,空间很小(通常只有几MB),函数结束自动销毁
堆内存空间大,动态分配,大小运行时确定,但是必须手动管理
原因1:【主要原因】运行时才知道需要多大内存
静态数组写大了浪费内存,写小了会溢出,动态内存要多少给多少
例如:用户输入、读取文件、网络接收的数据,程序跑起来才知道要多少数据,没有办法提前写死。
原因2:突破栈空间限制
栈空间很小,一般就1-8MB左右,大数据不能放在栈上,必须放到堆。
原因3:内存生命周期不受函数作用域限制
栈上的局部变量,函数执行完毕就自动释放了,不能继续函数外使用
动态内存:函数结束,堆内存依然存在,必须手动free才释放
eg:局部数组,函数返回后内存销毁,返回指针就是也指针
原因4:实现可边长数据结构
链表、队列、树、哈希表,全部离不开动态内存。
链表每新增一个节点,就要malloc一块内存节点(没有动态内存,就写不出链表)
二、四种常用动态内存函数
1、四种对比
| 函数 | 参数 | 内存初始化 | 功能 |
|---|---|---|---|
| malloc | malloc(字节数) | 不初始化(垃圾值) | 分配指定大小堆内存 |
| calloc | calloc(元素个数,每个大小) | 全部置0 | 分配数组,自动清零 |
| realloc | realloc(旧指针,新总字节) | 旧数据保留,新增部分不初始化 | 修改已分配内存大小,扩容缩容 |
| free | free(指针) | - | 释放堆内存 |
| malloc和realloc分配的空间生存期,是随进程持续性,不是随代码块持续性。 | |||
| 2、malloc | |||
| 作用:分配X字节的堆内存,不会初始化内存,里面为随机值。分配成功返回内存首地址,分配失败返回NULL |
#include<stdio.h>#include<windows.h>#include<math.h>intmain(){SetConsoleOutputCP(65001);SetConsoleCP(65001);//在堆上申请10个连续int内存空间int*p=(int*)malloc(10*sizeof(int));if(p==NULL){printf("动态内存分配失败\n");}else{printf("动态内存分配成功\n");for(inti=0;i<10;i++){printf("%p,存放的内容%lx\n",&p[i],p[i]);}}free(p);//用完释放p=NULL;// 置空,防止野指针return0;}运行结果:
malloc分配的内存不会自动初始化,所有元素默认是随机垃圾值,使用前建议手动赋值。
3、calloc
calloc(num,size)
作用:分配num个元素,每个占用size字节,分配完成自动全部初始化为0
和malloc的区别:calloc会清零,malloc不会。
//上面的malloc变为callocint*p=(int*)calloc(10,sizeof(int));运行结果:
4、realloc
realloc(ptr,new_size)
作用:重新修改已分配好的堆内存,扩容或缩容
ptr:之前得到的指针
newsize:新的总字节数
注意:
如果后面堆空间足够,直接原地扩容,返回原来的地址
如果后面不够,堆找一块全新内存,把旧内容复制过去,释放旧内存,返回新地址
失败返回NULL,原来旧内容不会释放
realloc(ptr,0):长度为0,是把原来的空间释放
#include<stdio.h>#include<windows.h>#include<math.h>intmain(){SetConsoleOutputCP(65001);SetConsoleCP(65001);//在堆上申请10个连续int内存空间,并将内存的首地址赋值给整型指针pint*p=(int*)calloc(5,sizeof(int));int*tmp=(int*)realloc(p,10*sizeof(int));if(p==NULL){printf("动态内存分配失败");}else{printf("-----动态内存分配成功-----\n");for(inti=0;i<5;i++){printf("%p,存放的内容%lx\n",&p[i],p[i]);}if(tmp!=NULL){printf("-----重新分配成功-----\n");p=tmp;for(inti=0;i<10;i++){printf("%p,存放的内容%lx\n",&p[i],p[i]);}}else{printf("-----重新分配失败-----\n");//分配失败,p仍指向旧内存,可以继续使用printf("%p\n",&p);}}free(p);//用完释放p=NULL;// 置空,防止野指针return0;}运行结果:
注意:realloc直接用原指针接收返回值,失败会导致内存泄漏(例子中直接写p=realloc(p,newsize);如果失败p被赋值为NULL,原内存地址丢失,直接内存泄漏)。
realloc(NULL,n)等价于malloc(n)
5、free释放内存
作用:释放堆内存,还给操作系统
规则:
参数必须是malloc、realloc、calloc返回的地址,不能传栈变量地址
free之后,原来的指针变量仍然保存旧地址,变为野指针,建议手动赋值为NULL
不能重复free同一个指针,程序会崩溃
free(NULL)是安全的,什么都不做
三、使用注意事项
1、实际项目大量使用动态内存(malloc、realloc、calloc、free),但是不能无脑使用,滥用会付出巨大代价:
内存泄漏:忘记free,程序越跑内存占用越高,长期运行服务直接卡死。
野指针:free之后,继续使用,重复free,直接崩溃
realloc坑点,分配失败处理麻烦
频繁malloc、free会产生内存碎片,长时间运行内存效率下降
所以:
小固定大小缓冲区,优先栈数组,不要malloc
malloc和free尽量配对,谁申请,谁释放
嵌入式设备内存很小,尽量少用动态内存,必要时禁止malloc
2、垃圾内存/内存泄漏
char*p=malloc(10);chara;p=&a;//p本来指向10字节的动态内存分配的空间,现在指向a上述代码,没有任何指针会指向那段动态内存分配的空间,并且那段空间不会释放,变为垃圾内存(或说内存泄漏)
解决办法:
方案一:p指向其他对象之前先释放
free(p);p=&a;方案二:p指向其他对象之前,给那段空间找一个索引指针
char*q=p;//后面可以通过q来释放这段空间。p=&a;