☰
17.动态内存
2026/10/9 1:16:13 网站建设 项目流程

一、为什么需要动态内存?

内存分两大类:栈区内存、堆区内存

栈区(自动内存)

变量:局部变量、数组,写在{}里面 特点:

✅ 自动分配、自动回收,不用手动释放

❌ 编译时代码就固定大小,运行中不能扩容

❌ 生命周期固定,出了大括号 / 函数,内存立刻回收

比喻:宿舍,房间大小固定,离校自动退房。

堆区(动态内存)

由程序员手动申请、手动释放 特点:

✅ 运行的时候才确定空间大小,可以扩容

✅ 生命周期自己掌控,不调用 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);

参数 ptr:malloc/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 种情况:

  1. 原内存后面还有足够连续空闲空间 → 直接向后拓展,返回原来的地址
  2. 后面空间不够 → 在堆里找一块全新连续空间,拷贝旧数据,释放旧空间,返回新地址
  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. 野指针

定义:指针存了地址,但是这片内存没有权限访问 产生原因:

  1. 函数返回栈区局部变量地址。函数结束栈内存回收,地址失效。
  2. free 释放堆内存之后,指针没有置 NULL,指针还保留旧地址。
  3. 指针没有初始化,随机垃圾地址。

危害:访问野指针 → 程序崩溃、乱码。 解决:free 后p=NULL;指针使用前判空。

2. 内存泄漏

定义:堆内存申请了,用完没有 free,并且把保存首地址的指针弄丢了,这块内存永远无法释放,一直占用资源。

产生场景:

  1. 保存堆首地址的指针被覆盖,原来 malloc 的地址丢失
  2. 函数内 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指向常量区字符串,二者内存位置完全不同。

易错 3:free 之后继续使用内存

char* str = malloc(100); strcpy(str, "hello"); free(str); if(str != NULL) { strcpy(str,"world"); //内存已经释放,非法访问 }

free 只是归还内存给操作系统,指针变量里地址不会自动清空,需要手动str=NULL。


五、动态内存使用规范(记住这几条,少踩坑)

  1. malloc/calloc/realloc 的返回值必须判断是否为 NULL
  2. free 只能释放堆内存,传入的必须是最原始 malloc 首地址
  3. free 之后立刻将指针置 NULL,防止野指针
  4. realloc 扩容用临时指针接收,不要直接覆盖原指针
  5. 堆内存使用完毕,一定要 free,防止内存泄漏
  6. 不要返回函数内部栈局部变量的地址

六、栈内存 vs 堆内存 对比总结表

表格

项目栈内存堆内存
申请方式定义局部变量、数组自动分配malloc/calloc/realloc 手动申请
大小编译时固定,运行不可变;容量很小(一般几 MB),超大数组会栈溢出运行时确定,可以 realloc 扩容;空间大,受系统可用内存限制
初始化局部变量默认随机垃圾值malloc 随机;calloc 全部置 0
释放时机出 {} / 函数结束自动回收程序员调用 free 手动释放
生命周期随作用域结束销毁直到 free 或者程序结束
比喻宿舍租房

七、常见错误清单(背诵)

  1. 不检查 malloc 返回值,空指针访问
  2. free 非堆空间(栈变量地址)
  3. free 偏移后的指针(p++ 后 free)
  4. 同一块内存多次 free
  5. free 之后继续读写这片内存
  6. realloc 直接赋值给原指针,扩容失败丢失地址
  7. malloc 后忘记 free,内存泄漏
  8. 函数返回栈上局部数组地址,产生野指针

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询