C-堆内存
堆内存
1、什么是堆内存
是进程中的一个内存段(heap),由程序员手动管理(手动申请和释放)
优点:足够大
缺点:使用麻烦且具备一定危险性
2、为什么要使用堆内存
1、随着程序的复杂,数据量增多,其他内存段已经不够用了
2、其它内存段的申请和释放不受控制,但是堆内存的申请和释放是受控制
3、如何使用堆内存malloc/free/calloc/realloc
注意:C语言中没有任何管理堆内存的语句,只能通过标准库的函数进行管理堆内存
- malloc
void *malloc(size_t size);
功能:从堆内存中申请size个字节的连续内存,内存中的数据值不确定
返回值:成功时返回该内存的首地址,失败时返回NULL
int* p = malloc(4);
- free
void free(void *ptr);
功能:释放一块连续的堆内存
free(p);
注意:free仅仅是释放使用权,但是内存中的数据不会全部清理
注意:不能重复释放,必须释放有效内存
注意:可以free(NULL),但无意义
- calloc(初始化为0)
void *calloc(size_t nmemb, size_t size);
功能:从堆内存中申请nmemb块(个数),每块size(每个的长度)个字节的内存,得到的依然是一块连续的内存
注意:通过calloc申请到的内存会被初始化为0
- realloc
void *realloc(void *ptr, size_t size);
功能:改变已有堆内存块的大小,
ptr:旧堆内存首地址
size:调整后的堆内存大小
返回值:成功返回调整后的堆内存首地址
注意:一定要重新接收,因为有可能不是在原地址上调整
如果无法在原基础上调整大小:
1、申请一块符合新要求的连续内存
2、拷贝旧内存中数据到新内存中
3、释放旧内存,并返回新内存首地址
4、malloc的内存管理机制
1、当首次通过malloc申请堆内存时,malloc会向操作系统申请内存,操作系统会直接分配33页(1页=4096字节)交给malloc管理,这样可以减少操作系统的运转次数,但是这样不意味着可以随意越界访问,因为malloc可能会继续分配给其他人,如果越界就会产生脏数据
int* p = malloc(4);
for(int i=0; i<1024*33-2; i++) //最多只能访问到倒数第二个元素位置,最后一个元素地址被用掉了
{
p[i] = 10;
}
2、连续通过malloc申请内存时,每个内存块之间会有一些空隙(4~12字节),这些空隙一部分是为了访问内存时对齐,可以加快内存的访问速度,其中有4个字节的空隙是为了记录malloc的维护信息,如果维护信息被破坏,会导致下一次free时出现内存崩溃
int* p1 = malloc(12);
int* p2 = malloc(12);
int* p3 = malloc(12);
printf("%p\n",p1); //0x84e8008
printf("%p\n",p2); //0x84e8018
printf("%p\n",p3); //0x84e8028
5、堆内存越界的后果
1、一切正常
2、段错误
3、脏数据
4、如果破坏了malloc的维护信息,会影响下一次free
6、使用堆内存需要注意的问题
6.1 内存泄漏
6.1.1 原因:
程序向系统申请分配了一块内存(new/malloc)给对象使用,程序使用完这块内存后没有释放(delete/free),导致这个不使用的对象一直占据内存单元,造成系统将不能再把它分配给需要的程序。即内存分配与内存释放没有做到一对一的匹配。
一些常见的导致内存泄漏的代码问题:
1)new[]和delete不匹配,或者malloc和free不匹配。
2)指针作为返回值时忘记释放。
3)指针被多次复制,只有一次释放。
4)忘记释放在try块中的内存。
5)忘记释放指向堆内存的全局变量指针。
6)使用循环分配内存时,忘记在循环结束时释放内存。
6.1.2 危害:
堆内存被不断的分配使用,没有及时回收,随着程序的运行,堆内存会慢慢的被消耗殆尽,当其他程序需要内存时,系统无法及时的分配合适的内存供其使用,将会造成程序崩溃。
6.1.3 如何避免内存泄露
谁申请的谁释放,谁知道该释放谁释放
6.1.4 定位内存泄漏
-
使用工具:使用专门的内存检测和分析工具,如Valgrind、Dr. Memory等;代码分析工具 mtrace。这些工具可以帮助你跟踪程序中的内存分配和释放操作,检测到潜在的内存泄漏。
-
重载new/delete操作符:在C++中,你可以重载全局的new和delete操作符,在其中添加自定义的跟踪代码来记录内存分配和释放情况,并进行统计和分析。
-
记录内存分配:在程序中追踪每次内存分配的位置和大小信息。可以通过重载全局的malloc/free函数,在其中添加额外的记录代码来实现。或者使用自定义的内存管理类,重写其alloc/free函数,在其中记录相应信息。
-
统计未释放资源:在程序退出前或者特定时间点,遍历已分配但未释放的资源列表,并输出相关信息。这样就可以确定哪些资源没有正确释放。
-
内存监控日志:在程序中添加日志功能,记录每次内存分配和释放操作以及相关调用栈信息。通过分析日志文件,可以找出潜在的泄漏点。
6.查看进程的内存使用情况
windows:任务管理器
Linux: ps -aux
6.2 内存碎片
已经释放了但是又无法使用的内存叫做内存碎片,是由于申请和释放的时间不协调导致的,只能尽量减少不能避免
如何减少内存碎片的产生:
1、尽量申请大块内存自己管理
2、不要频繁地申请释放内存
3、使用栈内存不会产生内存碎片
7、内存清理函数bzero/memset
- bzero: 置0
#include <strings.h>
void bzero(void *s, size_t n);
功能:把一块内存全部清理为0
s:内存块的首地址
n:要清理的内存字节数
- memset:置为c
#include <string.h>
void *memset(void *s, int c, size_t n);
功能:把内存块按字节设置为c
s:内存块的首地址
c:想要设置的ASCII码值
n:要设置的内存字节数
返回值:返回设置后的内存首地址 s
链式调用:一个函数的返回值可以作为另一个函数的参数
free(memset(p,0,100));
8、堆内存中定义二维数组
8.1 指针数组
类型名* arr[n];
for(int i=0; i<n; i++)
{
arr[i] = malloc(sizeof(类型)*m);
}
申请到 n行m列 的二维数组,每行内存可能不连续
注意:每一行的m值可不同,可以得到不规则的二维数组
缺点:容易产生内存碎片
优点:可以不规则、容易申请成功
8.2 数组指针
类型名 (*arrp)[m] = malloc(sizeof(类型)*m*n);
申请到 n行m列 的二维数组,并且全部内存都是连续的
优点:不容易产生内存碎片
缺点:相对而已对内存要求更高
注意:无论哪种方式申请,最后都是当做二维数组访问arr[i][j]
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」