当前位置: 首页 > news >正文

动态(堆区)内存管理与内存泄漏规避


个人主页: 流年如夢

专栏: 《C语言》

文章目录

  • 一.动态内存分配
    • 1.1为什么需要动态内存分配
  • 二.malloc与free
    • 2.1malloc(函数原型、功能)
    • 2.2free(函数原型、功能)
    • 2.3举例
  • 三.calloc与realloc
    • 3.1calloc(函数原型、功能)
    • 3.2realloc(函数原型、功能)
  • 四.常见的动态内存错误
    • 4.1对NULL(空指针)解引用
    • 4.2动态空间越界访问
    • 4.3对非动态内存free
    • 4.4只释放一部分动态内存
    • 4.5重复释放同一块内存
    • 4.6忘记释放导致内存泄漏
  • 五.柔性数组
    • 5.1声明
    • 5.2特点
    • 5.3如何使用
  • 六.C/C++程序内存区域划分(栈区、堆区、静态区)
  • 🎯总结
  • ⚠️易错点

Ladies and gentlemen,本篇文章讲的是动态内存管理,下面将带领大家学习malloc、free、calloc、realloc四大函数、动态内存常见错误、柔性数组与程序内存区域划分;全程高能,不容错过!!!
前言

在C语言中,普通变量与数组在栈区开辟空间,大小固定、无法更改。而实际开发中,常常需要在运行时决定内存大小、能够自由扩容与释放的空间。为此,C语言提供了动态内存管理机制,允许程序猿手动在堆区申请、使用、释放内存。本篇文章将带大家从基础用法到底层坑点全覆盖,彻底避开内存泄漏、野指针、越界等致命错误。

一.动态内存分配

1.1为什么需要动态内存分配

相对于我们平常用的内存开辟方式而言,动态内存的优势在于:

  1. 运行时按需申请、按需释放
  2. 空间大小可随时扩容或缩容
  3. 存放在堆区,空间充足

它弥补了空间大小固定不可变,例如:

intval=20;//栈上开辟4字节chararr[10]={0};//数组长度栈上开辟10字节

二.malloc与free

2.1malloc(函数原型、功能)

头文件为<stdlib.h>
函数原型

void*malloc(size_tsize);

功能

1.在堆区申请连续内存空间
2. 如果开辟成功,返回起始地址;如果开辟失败,返回NULL
3. 返回·void*·,由自己决定类型
4 .内存不初始化,内容随机

2.2free(函数原型、功能)

函数原型

voidfree(void*ptr);

功能:

  1. 释放或回收动态开辟的内存
  2. 如果ptrNULL,则什么都不做;如果ptr不是动态开辟,则行为未定义

2.3举例

#include<stdio.h>#include<stdlib.h>//头文件不能少了intmain(){intnum=0;scanf("%d",&num);int*ptr=(int*)malloc(num*sizeof(int));//申请num个int空间if(ptr!=NULL)//检查是否开辟成功{for(inti=0;i<num;i++){*(ptr+i)=0;}}free(ptr);ptr=NULL;return0;}

🧐分析:先申请numint空间,再检查是否开辟成功(ptr != NULL),最后free释放后手动将指针置NULL,避免野指针

三.calloc与realloc

3.1calloc(函数原型、功能)

函数原型

void*calloc(size_tnum,size_tsize);

功能

  1. num个大小为size的元素开辟空间
  2. 自动把内存初始化为0
  3. malloc唯一区别就是calloc会初始化

举个例子

#include<stdio.h>#include<stdlib.h>intmain(){int*p=(int*)calloc(10,sizeof(int));if(p!=NULL){for(inti=0;i<10;i++){printf("%d ",*(p+i));}}free(p);p=NULL;return0;}

🧐分析:先申请10个int,并全部初始化为0

运行结果

3.2realloc(函数原型、功能)

函数原型

void*realloc(void*ptr,size_tsize);

功能

  1. 对已动态开辟的内存重新调整大小
  2. ptr--> 要调整的内存地址
  3. size--> 调整后的新大小(字节)
  4. 返回调整后的起始地址

我们知道realloc是用来对已动态开辟的内存重新调整大小的,会有两种情况

  1. 原有空间后有足够空间,直接向后扩容
  2. 原有空间后无空间,重新找整块空间,拷贝数据后返回新地址

如何使用realloc,如下所示:

#include<stdio.h>#include<stdlib.h>intmain(){int*ptr=(int*)malloc(100);if(ptr==NULL){perror("malloc");return1;}int*tmp=(int*)realloc(ptr,1000);//<<=扩容,由100->1000if(tmp!=NULL){ptr=tmp;}//...free(ptr);ptr=NULL;return0;}

🧐分析绝对不能直接用原指针(ptr)接收realloc返回值,并且申请失败返回NULL,会导致原地址丢失,造成内存泄漏,应该用临时变量(tmp)在判断成功后再赋值

四.常见的动态内存错误

4.1对NULL(空指针)解引用

voidtest(){int*p=(int*)malloc(INT_MAX/4);*p=20;free(p);}

🧐分析malloc申请过大内存,极易开辟失败,返回NULL;上面代码没有判断指针是否为空,直接对NULL解引用;程序会直接崩溃,出现段错误;所以必须先判断p != NULL再使用

4.2动态空间越界访问

voidtest(){int*p=(int*)malloc(10*sizeof(int));for(inti=0;i<=10;i++){*(p+i)=i;}free(p);}

🧐分析:申请了10int空间,在for循环里最后i=10,但我们的下标最大只有9,超出申请范围,导致内存越界;所以我们应该将i<=10改成i<10即可

4.3对非动态内存free

voidtest(){inta=10;int*p=&a;free(p);}

🧐分析:变量a是局部变量,是在栈区,然而free只能释放堆区的动态内存(如malloc、calloc、realloc);我们知道,栈内存由系统自动释放,用不上free

4.4只释放一部分动态内存

voidtest(){int*p=(int*)malloc(100);p++;free(p);}

🧐分析p++后,指针不再指向动态内存的起始地址;free只能释放申请时返回的起始地址;此时调用free,导致非法释放;所以不能移动原指针或者用临时指针遍历

4.5重复释放同一块内存

voidtest(){int*p=(int*)malloc(100);free(p);free(p);}

🧐分析:已经free过一次了,再free一次是属于重复释放,会导致堆破坏,程序崩溃;free过后应该让p成为空指针更安全,即p=NULL

4.6忘记释放导致内存泄漏

voidtest(){int*p=(int*)malloc(100);}

🧐分析当函数使用完后指针p会被销毁,但依然会占用堆内存,从而造成内存泄漏,长期运行会耗尽内存;我们应该在不使用的时候让指针free,即free(p)

五.柔性数组

C99允许结构体最后一个成员是未知大小的数组,被称为柔性数组

5.1声明

structst_type{inti;inta[];//<--这个便是柔性数组成员(举例)};

5.2特点

如下:

  1. 前面至少有一个成员(例如上面int a[]前面有一个int i
  2. sizeof不计算柔性数组大小
  3. 必须用malloc动态分配空间

5.3如何使用

举例(分配结构体再加上100个int空间):

typedefstructst_type{inti;inta[];}type_a;intmain(){type_a*p=(type_a*)malloc(sizeof(type_a)+100*sizeof(int));p->i=100;for(inti=0;i<100;i++){p->a[i]=i;}free(p);p=NULL;return0;}

🧐分析:在这里只需一次free,方便释放;内存连续,访问更快、碎片更少

六.C/C++程序内存区域划分(栈区、堆区、静态区)

区域名称存放内容管理方式特点
内核空间系统内核、驱动操作系统用户不可读写
栈区局部变量、函数参数、返回地址自动创建、自动销毁空间小、速度快、地址向下增长
内存映射段文件映射、动态库操作系统用于共享库、文件映射
堆区malloc、calloc、realloc动态内存手动申请、手动释放空间大、灵活、易产生内存碎片、地址向上增长
静态区(数据段)全局变量、static变量程序结束释放生命周期贯穿整个程序
代码段执行代码、只读常量操作系统只读、不可修改

相关截图如下所示:

🎯总结

  1. 动态内存函数malloccallocreallocfree,都在头文件<stdlib.h>
  2. malloc申请不初始化;calloc申请并初始化为 0
  3. realloc扩容必须用临时变量接收,防止丢失地址
  4. 必须检查返回值是否为NULL(空指针),避免空指针崩溃
  5. 动态内存常见错误:空指针、越界、非法free、重复free、内存泄漏
  6. 柔性数组方便释放、内存连续,是结构体动态扩容的最佳方案
  7. 栈区自动管理,堆区手动管理,分清区域避免野指针

⚠️易错点

  1. 不检查malloc返回值,直接使用导致崩溃
  2. realloc直接赋值给原指针,造成内存泄漏
  3. free后不置空,产生野指针
  4. 栈空间地址返回给上层使用,非法访问
  5. 传值调用想修改指针,导致无法分配成功
  6. 忘记释放动态内存,造成长期内存泄漏
  7. 柔性数组前面无成员、或用非动态方式创建

👀 关注我们一路同行,从入门到大师,慢慢沉淀、稳步成长
❤️ 点赞鼓励原创,让优质内容被更多人看见
⭐ 收藏收好核心知识点与实战技巧,需要时随时查阅
💬 评论分享你的疑问或踩坑经历,一起交流避坑、共同进步

http://www.jsqmd.com/news/696030/

相关文章:

  • 2026年3月靠谱的石英仪器机构推荐,石英管/石英棒/石英板/石英器皿/石英制品/蓝宝石制品/石英片,石英仪器厂家哪个好 - 品牌推荐师
  • Perl 5完全指南:从零开始掌握经典编程语言的10个核心技巧
  • 保姆级教程:用Vector Davinci Configurator搞定AUTOSAR CAN通信协议栈(从DBC导入到错误清零)
  • 风洞实验(建议读微型扑翼飞行器风洞实验方法与应用研究)(要求根据课程、课本、试验报告,撰写完备的报告)
  • 如何快速提升spaCy NLP能力:使用预训练转换器模型的完整指南
  • 从antfu/skills项目学习:如何构建动态个人技能全景图与知识体系
  • 数据结构-双向链表【详细解析,包含注意事项】
  • Figma设计稿一键转代码:基于MCP协议的AI编码助手实践
  • ml-intern未来发展:AI助手的演进方向
  • 探索地下环境的终极智能规划利器:GBPlanner_ROS完整指南
  • 从SPICE到IBIS:如何为你的高速电路设计选择最佳仿真模型
  • Optuna超参数优化:提升机器学习模型调优效率
  • 2026年国内可靠钎焊材料企业排行及核心能力解析:活性钎料、焊带、焊接加工、焊片、焊环、粘带焊料、膏状助焊剂285选择指南 - 优质品牌商家
  • 如何精准计算AWS io2卷成本?OpenCost的终极技术解析
  • Hayase社区参与指南:如何加入讨论、报告问题和提出建议
  • 2026年3月AMERICAN DENKI(美国电器)插头插座厂家推荐,AMERICAN DENKI(美国电器)插头插座供应商技术实力与市场口碑 - 品牌推荐师
  • grpc-swift异步编程实战:Async/Await与SwiftNIO完美结合
  • 基于多域特征融合与生成对抗网络的故障诊断方法
  • 如何用夸克自动转存实现智能追更:从零开始的终极教程
  • Qwen-Agent智能体框架:从大模型到可执行AI应用的开发指南
  • 沁恒CH57x undefined reference to “SPI0_MasterDefInit“ | CHxxx No such file or directory
  • ARM SME2指令集:矩阵运算与饱和算术优化
  • 机器学习测试数据集:原理与应用实战
  • HyperAgents框架:构建具备思考-行动循环的LLM智能体实战指南
  • 如何快速掌握设计到动画转换:AEUX终极指南助你5分钟完成无缝衔接
  • 保姆级教程:在Ubuntu系统的AIxBoard上,用CODESYS V3.5 SP17配置软PLC,并打通Python(OpenVINO/YOLOv5)的共享内存通信
  • 国内氟橡胶表带开模专业厂家排行:液态硅胶手表带开模/TPU手表带/固态硅胶手表带开模/固态硅胶表带开模/氟橡胶手表带开模/选择指南 - 优质品牌商家
  • Zip4j流式处理实战:高效处理大文件与内存优化技巧
  • Hologram社区生态全景:从主题插件到扩展工具完全解析
  • PHP GitHub API测试驱动开发:如何编写高质量的API客户端测试