第22讲:动态内存管理
📚 第22讲:动态内存管理 🧠💥
从“栈上小打小闹”到“堆上自由驰骋”,掌握C语言的“内存遥控器”!
告别固定大小的束缚,让程序按需生长!
📂 目录
- 为什么要有动态内存分配 —— 程序的“弹性需求” 🌱
malloc
和free
—— 申请与释放的“黄金搭档” 💰calloc
和realloc
—— 智能初始化与灵活扩容 📏- 常见的动态内存错误 —— 程序员的“十大禁令” ⚠️
- 动态内存经典笔试题分析 —— 面试官最爱的“陷阱题” 🎣
- 柔性数组 —— 结构体中的“可变长字段” 🔄
- C/C++程序内存区域划分 —— 内存世界的“地图” 🗺️
📖 正文开始
前面我们用 int a[10];
在栈上开辟空间,简单直接。
但问题来了:
- 数组大小必须编译时确定?
- 程序运行中才知道要多大怎么办?
- 数组开小了,能“长大”吗?
这时候,动态内存管理就登场了!它让我们在堆区自由申请、释放内存,灵活无比!
为什么要有动态内存分配 —— 程序的“弹性需求” 🌱
🌟 生活比喻:租房 vs 买房
-
栈空间
:像“租房”——大小固定,到期自动退房。
int arr[10]; // 房子大小10,不能改
-
堆空间
:像“买地自建房”——想多大就多大,自己管理。
int* p = malloc(n * sizeof(int)); // 按需申请
✅ 动态内存分配的核心:程序运行时才知道要多少空间!
malloc
和 free
—— 申请与释放的“黄金搭档” 💰
🔹 malloc
:申请内存
void* malloc(size_t size);
✅ 特点:
- 成功:返回指向连续空间的指针。
- 失败:返回
NULL
→ 必须检查! - 返回
void*
→ 需要强制类型转换。 size
为0 → 行为未定义(取决于编译器)。
🔹 free
:释放内存
void free(void* ptr);
✅ 特点:
- 专门释放
malloc
/calloc
/realloc
申请的内存。 ptr
为NULL
→ 什么都不做(安全)。ptr
不是动态内存 → 未定义行为!危险!
✅ 完整示例:动态数组
#include <stdio.h>
#include <stdlib.h>int main() {int num;scanf("%d", &num);int* ptr = (int*)malloc(num * sizeof(int));if (ptr == NULL) {printf("内存申请失败!\n");return 1;}for (int i = 0; i < num; i++) {ptr[i] = i;}free(ptr); // ✅ 释放ptr = NULL; // ✅ 避免野指针(强烈推荐!)return 0;
}
🔑 黄金法则:
malloc
和free
必须成对出现!
calloc
和 realloc
—— 智能初始化与灵活扩容 📏
🔹 calloc
:带初始化的申请
void* calloc(size_t num, size_t size);
- 功能:申请
num
个大小为size
的元素,并自动初始化为0。 - 与
malloc
唯一区别:初始化。
✅ 示例:
int* p = (int*)calloc(10, sizeof(int));
// 输出:0 0 0 0 0 0 0 0 0 0
✅ 如果需要清零,优先用
calloc
!
🔹 realloc
:灵活调整大小
void* realloc(void* ptr, size_t size);
✅ 两种情况:
情况 | 行为 |
---|---|
原地扩容 | 后面有足够空间 → 直接追加,返回原地址 |
异地扩容 | 后面不够 → 找新空间,拷贝数据,释放旧空间,返回新地址 |
⚠️ 使用陷阱:直接赋值危险!
// ❌ 危险!如果realloc失败,ptr变成NULL,原内存丢失!
ptr = (int*)realloc(ptr, 1000);// ✅ 正确做法:用临时指针
int* temp = (int*)realloc(ptr, 1000);
if (temp != NULL) {ptr = temp; // 确认成功后再更新
}
✅ 安全第一!永远不要直接覆盖原指针!
常见的动态内存错误 —— 程序员的“十大禁令” ⚠️
🔴 禁令1:对 NULL
指针解引用
int* p = malloc(INT_MAX);
*p = 20; // ❌ 如果malloc失败,p是NULL,崩溃!
✅ 解决:永远检查
malloc
返回值!
🔴 禁令2:越界访问
int* p = malloc(10 * sizeof(int));
for (int i = 0; i <= 10; i++) { // i=10时越界!p[i] = i;
}
✅ 解决:循环条件写对!
🔴 禁令3:释放非动态内存
int a = 10;
int* p = &a;
free(p); // ❌ p不是malloc来的!未定义行为!
✅ 解决:
free
只用于malloc/calloc/realloc
!
🔴 禁令4:释放部分内存
int* p = malloc(100);
p++;
free(p); // ❌ p不再指向起始位置!
✅ 解决:
free
时指针必须指向malloc
返回的地址!
🔴 禁令5:重复释放
free(p);
free(p); // ❌ 重复释放!可能崩溃!
✅ 解决:释放后立即将指针置为
NULL
。
🔴 禁令6:内存泄漏
void test() {int* p = malloc(100);// 忘记free!函数结束,指针消失,内存永远丢失!
}
✅ 解决:谁申请,谁释放! 每次
malloc
都要想好在哪里free
。
动态内存经典笔试题分析 —— 面试官最爱的“陷阱题” 🎣
💣 题目1:传值陷阱
void GetMemory(char* p) {p = malloc(100); // ❌ 修改的是形参p,不影响str
}
void Test() {char* str = NULL;GetMemory(str);strcpy(str, "hello"); // ❌ str仍是NULL,崩溃!
}
✅ 正确:传二级指针!
💣 题目2:返回栈空间
char* GetMemory() {char p[] = "hello world"; // p在栈上return p; // ❌ 函数结束,p被销毁,返回野指针!
}
✅ 错误:返回局部数组地址!
💣 题目3:正确使用二级指针
void GetMemory(char** p, int num) {*p = malloc(num); // ✅ 修改str指向的地址
}
void Test() {char* str = NULL;GetMemory(&str, 100); // ✅ 成功申请strcpy(str, "hello"); // ✅ 安全写入
}
✅ 正确!输出:
hello
💣 题目4:释放后使用
char* str = malloc(100);
strcpy(str, "hello");
free(str);
if (str != NULL) { // str值可能未变,但内存已释放!strcpy(str, "world"); // ❌ 使用已释放内存!未定义行为!
}
✅ 解决:释放后立即将
str = NULL
。
柔性数组 —— 结构体中的“可变长字段” 🔄
🌟 生活比喻:可伸缩的行李箱
普通结构体像“固定大小的箱子”,而柔性数组像“可拉伸的行李箱”!
✅ 什么是柔性数组?
C99 允许结构体最后一个成员是未知大小的数组:
struct st_type {int i;int a[]; // 柔性数组成员
};
⚠️ 有些编译器不支持
[]
,可用a[0]
替代。
🔍 柔性数组的特点
- 前面至少有一个其他成员。
sizeof(结构体)
不包含柔性数组的内存。- 必须用
malloc
动态分配,且分配空间 >sizeof(结构体)
。
printf("%d\n", sizeof(struct st_type)); // 输出:4 (只有int i)
✅ 使用方式
struct st_type* p = malloc(sizeof(struct st_type) + 100 * sizeof(int));
p->i = 100;
for (int i = 0; i < 100; i++) {p->a[i] = i;
}
free(p); // ✅ 一次释放!
🆚 柔性数组 vs 指针成员
方式 | 代码 | 优点 |
---|---|---|
柔性数组 | int a[]; | 1. 一次 malloc /free 2. 内存连续,访问快 |
指针成员 | int* p_a; | 需要两次 malloc 和两次 free |
✅ 推荐:优先使用柔性数组,更安全、高效!
C/C++程序内存区域划分 —— 内存世界的“地图” 🗺️
🌍 五大区域
区域 | 特点 | 存放内容 |
---|---|---|
栈区 (stack) | 自动管理,高效 | 局部变量、函数参数、返回地址 |
堆区 (heap) | 手动管理,灵活 | malloc /new 申请的内存 |
数据段 (静态区) | 程序运行期间存在 | 全局变量、static 变量 |
代码段 | 只读 | 函数代码、字符串常量 |
常量区 | 只读 | const 变量、字符串字面量 |
🧠 内存布局图解
高地址
+------------------+
| 栈区 | ← 局部变量,向下增长
+------------------+
| |
| 堆区 | ← malloc,向上增长
| |
+------------------+
| 全局/静态区 | ← static, 全局变量
+------------------+
| 常量区 | ← "hello", const
+------------------+
| 代码段 | ← 函数体
+------------------+
低地址
✅ 理解内存布局,是成为高级C程序员的必经之路!
🎯 总结:动态内存“三要三不要”
要 ✅ | 不要 ❌ |
---|---|
要检查 malloc 返回值 | 不要对 NULL 解引用 |
要 free 后置 NULL | 不要释放非动态内存 |
要成对使用 malloc/free | 不要忘记释放(内存泄漏) |
🎯 恭喜你!
你已经掌握了C语言中最强大也最危险的工具——动态内存管理!
记住:能力越大,责任越大!