个人主页 流年如夢专栏 《C语言》文章目录一.动态内存分配1.1为什么需要动态内存分配二.malloc与free2.1malloc函数原型、功能2.2free函数原型、功能2.3举例三.calloc与realloc3.1calloc函数原型、功能3.2realloc函数原型、功能四.常见的动态内存错误4.1对NULL空指针解引用4.2动态空间越界访问4.3对非动态内存free4.4只释放一部分动态内存4.5重复释放同一块内存4.6忘记释放导致内存泄漏五.柔性数组5.1声明5.2特点5.3如何使用六.C/C程序内存区域划分栈区、堆区、静态区总结⚠️易错点Ladies and gentlemen本篇文章讲的是动态内存管理下面将带领大家学习malloc、free、calloc、realloc四大函数、动态内存常见错误、柔性数组与程序内存区域划分全程高能不容错过前言在C语言中普通变量与数组在栈区开辟空间大小固定、无法更改。而实际开发中常常需要在运行时决定内存大小、能够自由扩容与释放的空间。为此C语言提供了动态内存管理机制允许程序猿手动在堆区申请、使用、释放内存。本篇文章将带大家从基础用法到底层坑点全覆盖彻底避开内存泄漏、野指针、越界等致命错误。一.动态内存分配1.1为什么需要动态内存分配相对于我们平常用的内存开辟方式而言动态内存的优势在于运行时按需申请、按需释放空间大小可随时扩容或缩容存放在堆区空间充足它弥补了空间大小固定不可变例如intval20;//栈上开辟4字节chararr[10]{0};//数组长度栈上开辟10字节二.malloc与free2.1malloc函数原型、功能头文件为stdlib.h函数原型void*malloc(size_tsize);功能1.在堆区申请连续内存空间2. 如果开辟成功返回起始地址如果开辟失败返回NULL3. 返回·void*·由自己决定类型4 .内存不初始化内容随机2.2free函数原型、功能函数原型voidfree(void*ptr);功能释放或回收动态开辟的内存如果ptr是NULL则什么都不做如果ptr不是动态开辟则行为未定义2.3举例#includestdio.h#includestdlib.h//头文件不能少了intmain(){intnum0;scanf(%d,num);int*ptr(int*)malloc(num*sizeof(int));//申请num个int空间if(ptr!NULL)//检查是否开辟成功{for(inti0;inum;i){*(ptri)0;}}free(ptr);ptrNULL;return0;}分析先申请num个int空间再检查是否开辟成功ptr ! NULL最后free释放后手动将指针置NULL避免野指针三.calloc与realloc3.1calloc函数原型、功能函数原型void*calloc(size_tnum,size_tsize);功能为num个大小为size的元素开辟空间自动把内存初始化为0与malloc唯一区别就是calloc会初始化举个例子#includestdio.h#includestdlib.hintmain(){int*p(int*)calloc(10,sizeof(int));if(p!NULL){for(inti0;i10;i){printf(%d ,*(pi));}}free(p);pNULL;return0;}分析先申请10个int并全部初始化为0运行结果3.2realloc函数原型、功能函数原型void*realloc(void*ptr,size_tsize);功能对已动态开辟的内存重新调整大小ptr-- 要调整的内存地址size-- 调整后的新大小字节返回调整后的起始地址我们知道realloc是用来对已动态开辟的内存重新调整大小的会有两种情况原有空间后有足够空间直接向后扩容原有空间后无空间重新找整块空间拷贝数据后返回新地址如何使用realloc如下所示#includestdio.h#includestdlib.hintmain(){int*ptr(int*)malloc(100);if(ptrNULL){perror(malloc);return1;}int*tmp(int*)realloc(ptr,1000);//扩容由100-1000if(tmp!NULL){ptrtmp;}//...free(ptr);ptrNULL;return0;}分析绝对不能直接用原指针(ptr)接收realloc返回值,并且申请失败返回NULL会导致原地址丢失造成内存泄漏应该用临时变量(tmp)在判断成功后再赋值四.常见的动态内存错误4.1对NULL空指针解引用voidtest(){int*p(int*)malloc(INT_MAX/4);*p20;free(p);}分析malloc申请过大内存极易开辟失败返回NULL上面代码没有判断指针是否为空直接对NULL解引用程序会直接崩溃出现段错误所以必须先判断p ! NULL再使用4.2动态空间越界访问voidtest(){int*p(int*)malloc(10*sizeof(int));for(inti0;i10;i){*(pi)i;}free(p);}分析申请了10个int空间在for循环里最后i10但我们的下标最大只有9超出申请范围导致内存越界所以我们应该将i10改成i10即可4.3对非动态内存freevoidtest(){inta10;int*pa;free(p);}分析变量a是局部变量是在栈区然而free只能释放堆区的动态内存如malloc、calloc、realloc我们知道栈内存由系统自动释放用不上free4.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成为空指针更安全即pNULL4.6忘记释放导致内存泄漏voidtest(){int*p(int*)malloc(100);}分析当函数使用完后指针p会被销毁但依然会占用堆内存从而造成内存泄漏长期运行会耗尽内存我们应该在不使用的时候让指针free即free(p)五.柔性数组C99允许结构体最后一个成员是未知大小的数组被称为柔性数组5.1声明structst_type{inti;inta[];//--这个便是柔性数组成员举例};5.2特点如下前面至少有一个成员例如上面int a[]前面有一个int isizeof不计算柔性数组大小必须用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-i100;for(inti0;i100;i){p-a[i]i;}free(p);pNULL;return0;}分析在这里只需一次free方便释放内存连续访问更快、碎片更少六.C/C程序内存区域划分栈区、堆区、静态区区域名称存放内容管理方式特点内核空间系统内核、驱动操作系统用户不可读写栈区局部变量、函数参数、返回地址自动创建、自动销毁空间小、速度快、地址向下增长内存映射段文件映射、动态库操作系统用于共享库、文件映射堆区malloc、calloc、realloc动态内存手动申请、手动释放空间大、灵活、易产生内存碎片、地址向上增长静态区数据段全局变量、static变量程序结束释放生命周期贯穿整个程序代码段执行代码、只读常量操作系统只读、不可修改相关截图如下所示总结动态内存函数malloc、calloc、realloc、free都在头文件stdlib.hmalloc申请不初始化calloc申请并初始化为 0realloc扩容必须用临时变量接收防止丢失地址必须检查返回值是否为NULL空指针避免空指针崩溃动态内存常见错误空指针、越界、非法free、重复free、内存泄漏柔性数组方便释放、内存连续是结构体动态扩容的最佳方案栈区自动管理堆区手动管理分清区域避免野指针⚠️易错点不检查malloc返回值直接使用导致崩溃realloc直接赋值给原指针造成内存泄漏free后不置空产生野指针栈空间地址返回给上层使用非法访问传值调用想修改指针导致无法分配成功忘记释放动态内存造成长期内存泄漏柔性数组前面无成员、或用非动态方式创建 关注我们一路同行从入门到大师慢慢沉淀、稳步成长❤️ 点赞鼓励原创让优质内容被更多人看见⭐ 收藏收好核心知识点与实战技巧需要时随时查阅 评论分享你的疑问或踩坑经历一起交流避坑、共同进步