17.动态内存
一、为什么需要动态内存内存分两大类栈区内存、堆区内存栈区自动内存变量局部变量、数组写在{}里面 特点✅ 自动分配、自动回收不用手动释放❌ 编译时代码就固定大小运行中不能扩容❌ 生命周期固定出了大括号 / 函数内存立刻回收比喻宿舍房间大小固定离校自动退房。堆区动态内存由程序员手动申请、手动释放 特点✅ 运行的时候才确定空间大小可以扩容✅ 生命周期自己掌控不调用 free 就一直存在❌ 用完必须手动释放不然会内存泄漏比喻租房你自己决定租多大、什么时候退房。一句话总结栈内存大小固定、自动回收堆内存灵活程序员全权管理。二、4 个核心动态内存函数都在 stdlib.h 头文件1. malloc —— 在堆上申请一块内存函数原型void* malloc(size_t size);参数 size想要申请多少字节 返回值 申请成功返回堆空间第一个字节的地址 申请失败内存不够返回 NULL 空指针重点void*通用指针不知道你存什么类型必须强制转换成对应类型指针才能使用 malloc 开辟的内存不会初始化里面是随机垃圾值 ⚠️ malloc 返回值一定要判 NULL不判断会空指针访问崩溃申请失败要终止后续操作示例//申请40字节存10个int int* p (int*)malloc(10 * sizeof(int)); if(p NULL) { printf(内存申请失败\n); return 0; }2. free —— 释放堆内存还给操作系统函数原型void free(void* ptr);参数 ptrmalloc/calloc/realloc 返回的堆内存起始地址规则 ✅ 只能释放堆区内存不能 free 栈变量地址 ✅ 只能传最开始 malloc 拿到的首地址不能传偏移后的地址p 之后不能 free ✅ 同一块内存不能多次 free程序崩溃好习惯free 之后把指针赋值 NULL消除野指针free(p); p NULL;3. calloc —— 申请内存 自动初始化为 0函数原型void* calloc(size_t num, size_t size);参数num 元素个数size 每个元素占多少字节和 malloc 区别 malloc申请 size 字节内容随机垃圾值 calloc申请 num*size 字节全部字节自动置 0适合场景申请数组需要初始值全部是 0 的时候//申请10个int全部初始化为0 int* p (int*)calloc(10, sizeof(int));4. realloc —— 对已经申请好的堆内存扩容 / 缩容函数原型void* realloc(void* ptr, size_t new_size);参数ptr 原来堆内存地址new_size 想要的新总字节数扩容分 3 种情况原内存后面还有足够连续空闲空间 → 直接向后拓展返回原来的地址后面空间不够 → 在堆里找一块全新连续空间拷贝旧数据释放旧空间返回新地址内存不足找不到空间 → 返回 NULL原来内存不会释放⚠️ 不要直接写p realloc(p,新大小)扩容失败会把 p 覆盖成 NULL丢失原内存地址造成内存泄漏。 正确写法用临时指针接收int* tmp realloc(p, 20 * sizeof(int)); if(tmp ! NULL) { p tmp; }补充realloc(ptr,0)释放 ptr 指向内存返回值视编译器而定realloc(NULL, size)等价于malloc(size)三、2 个致命问题野指针 内存泄漏1. 野指针定义指针存了地址但是这片内存没有权限访问 产生原因函数返回栈区局部变量地址。函数结束栈内存回收地址失效。free 释放堆内存之后指针没有置 NULL指针还保留旧地址。指针没有初始化随机垃圾地址。危害访问野指针 → 程序崩溃、乱码。 解决free 后pNULL指针使用前判空。2. 内存泄漏定义堆内存申请了用完没有 free并且把保存首地址的指针弄丢了这块内存永远无法释放一直占用资源。产生场景保存堆首地址的指针被覆盖原来 malloc 的地址丢失函数内 malloc没有 free保存地址的局部变量出函数销毁补充知识点程序正常退出时操作系统会回收该程序全部堆内存。内存泄漏的危害主要体现在长时间持续运行的程序短程序直接退出泄漏影响不大。 现象程序越跑占内存越高长时间运行直接卡死。四、高频易错代码分析面试必考易错 1传一级指针函数内 malloc外部拿不到地址void GetMemory(char* p) { p (char*)malloc(100); } //调用 char* str NULL; GetMemory(str); //值传递调用函数时把str的值拷贝一份给形参p函数内部修改副本p不会影响外面原始str变量 strcpy(str, hello); //str依旧是NULL崩溃✅ 正确写法传二级指针void GetMemory(char** p) { *p (char*)malloc(100); } //调用 char* str NULL; GetMemory(str);易错 2返回栈数组地址char* GetMemory(void) { char p[] hello world; //栈数组 return p; //函数结束p内存回收返回野指针 ❌错误 }区分char* GetMemory(void) { char* p hello world; //字符串字面量放在只读常量区生命周期是整个程序 return p; //✅可以返回 }一句话总结char p[]是栈数组char* p指向常量区字符串二者内存位置完全不同。易错 3free 之后继续使用内存char* str malloc(100); strcpy(str, hello); free(str); if(str ! NULL) { strcpy(str,world); //内存已经释放非法访问 }free 只是归还内存给操作系统指针变量里地址不会自动清空需要手动strNULL。五、动态内存使用规范记住这几条少踩坑malloc/calloc/realloc 的返回值必须判断是否为 NULLfree 只能释放堆内存传入的必须是最原始 malloc 首地址free 之后立刻将指针置 NULL防止野指针realloc 扩容用临时指针接收不要直接覆盖原指针堆内存使用完毕一定要 free防止内存泄漏不要返回函数内部栈局部变量的地址六、栈内存 vs 堆内存 对比总结表表格项目栈内存堆内存申请方式定义局部变量、数组自动分配malloc/calloc/realloc 手动申请大小编译时固定运行不可变容量很小一般几 MB超大数组会栈溢出运行时确定可以 realloc 扩容空间大受系统可用内存限制初始化局部变量默认随机垃圾值malloc 随机calloc 全部置 0释放时机出 {} / 函数结束自动回收程序员调用 free 手动释放生命周期随作用域结束销毁直到 free 或者程序结束比喻宿舍租房七、常见错误清单背诵不检查 malloc 返回值空指针访问free 非堆空间栈变量地址free 偏移后的指针p 后 free同一块内存多次 freefree 之后继续读写这片内存realloc 直接赋值给原指针扩容失败丢失地址malloc 后忘记 free内存泄漏函数返回栈上局部数组地址产生野指针