

文章目录
一、动态内存管理
C语言的动态内存管理。这可是C语言的“精髓”之一,也是新手(甚至老手)的“噩梦”之源
它就像给了你一个超能力:你可以在程序运行时,向上帝(操作系统)“借”一块内存,用完了再“还”回去
🧰 1)为什么需要动态内存管理?
我们先看看“不动态”的内存是什么样的:
- 静态/全局内存 (Static/Global):
int global_var = 10;
这些变量在程序一开始运行时就被分配了,直到程序结束才释放。它们“长命百岁”,但不够灵活。

- 自动内存 (Automatic / Stack)
void my_func() { int local_var = 5; }
函数里的局部变量,放在“栈”上。它们的好处是“自动”:函数一调用,它们就出生;函数一返回,它们就去世并自动释放
- 动态内存(Heap / 堆)
从一块叫堆(Heap) 的巨大内存池里按需分配的
🧰 2)核心工具箱:四个关键函数
🌊🌊所有动态内存管理都围绕着 <stdlib.h> 头文件里的四个函数🌊🌊
1)malloc - 内存分配
-
原型:
void* malloc(size_t size); -
作用: 你告诉它你需要多少字节(
size),它会去堆里找一块这么大的、连续的空闲内存 -
返回值:
-
- 成功: 返回一个
void*类型的指针,指向这块内存的起始地址。void*是“通用指针”,你需要将它强制类型转换为你需要的类型(比如int*,char*等)。
- 成功: 返回一个
-
- 失败: 如果系统没钱(内存)了,它会返回
NULL
- 失败: 如果系统没钱(内存)了,它会返回
// 申请一块足够存放 100 个整数的内存
int* my_array = (int*)malloc(100 * sizeof(int));
// 🚨 **极其重要:永远检查返回值!**
if (my_array == NULL) {
// 内存分配失败了!
printf("天呐!内存不足!\n");
// 此时应该做一些错误处理,比如退出程序
return 1;
}
// 现在你可以像使用普通数组一样使用它
my_array[0] = 10;
my_array[99] = 123;
2)calloc - 连续分配
-
原型:
void* calloc(size_t num_elements, size_t element_size); -
作用: 你告诉它你需要多少个元素(
num_elements)以及每个元素多大(element_size)。 -
与
malloc的区别: -
- 参数更直观(比如
calloc(100, sizeof(int)))。
- 参数更直观(比如
-
- 它会自动把分配的内存全部初始化为
0malloc不会,malloc给你的内存里可能是任何“垃圾数据”
- 它会自动把分配的内存全部初始化为
3)realloc - 重新分配
-
原型:
void* realloc(void* ptr, size_t new_size); -
作用: 调整
ptr指向的那块内存的大小为new_size
注意:
1. 如果新大小new_size小于 原大小,它会“截断”多余的内存
2. 如果new_size大于 原大小,它会尝试在原地扩容
3. 如果原地空间不够(这很常见),realloc会在堆上另找一个足够大的新地方,把旧内存里的数据复制到新地方,然后释放旧的内存,最后返回新地址。
正因如此,你必须用一个新指针接收 realloc 的返回值!
// 之前申请了 100 个整数
int* my_array = (int*)malloc(100 * sizeof(int));
// ... 使用 ...
// 发现不够用,需要 200 个
int* bigger_array = (int*)realloc(my_array, 200 * sizeof(int));
if (bigger_array == NULL) {
// realloc 失败时,原指针 my_array 仍然有效
// 可以继续使用原来的内存块
} else {
// 成功了,现在 my_array 可能已经失效了
// 应该用 bigger_array 代替
my_array = bigger_array; //realloc成功时,用新指针替换旧指针
}
4)free - 释放
-
原型:
void free(void* ptr); -
作用: 告诉操作系统,
ptr指向的这块动态内存你不用了,可以回收了 -
规则:
-
- 你只能
free通过malloc,calloc,realloc得到的指针。
- 你只能
-
free(NULL)是安全的,什么也不做
int* my_array = (int*)malloc(100 * sizeof(int));
// ... 尽情使用 ...
// 好了,用完了,必须释放
free(my_array);
🧰 3)动态内存的注意事项
3.1)动态开辟内存忘记释放(内存泄漏)
- “只借不还”
1.你malloc了一块内存,但用完后忘记free。这块内存就“丢失”了。你的程序还在运行,但它既不能使用这块内存(因为你把指向它的指针弄丢了),系统也不能把它分给别人。
2.如果你的程序(比如服务器)长时间运行,并且不断地泄漏内存,它最终会耗尽系统所有内存,然后崩溃。

3.2)野指针
-
“你退了房,但还拿着钥匙。”
你free(ptr)之后,ptr指针本身的值(那个地址)并没有变,但它指向的内存已经不属于你了。 -
问题:
如果你稍后不小心又通过ptr去读写那块内存,你就是在“非法闯入”。你可能会读到垃圾数据,或者(更糟的)你可能会破坏掉其他人(程序里的另一部分)正在使用的数据。 -
最佳实践:
free 之后,立即将指针设为NULL。
free(my_array);
my_array = NULL; // 好习惯!
// 之后如果不小心访问
if (my_array != NULL) {
my_array[0] = 5; // 这段代码就不会执行了
}
3.3)重复释放
- “同一张账单付了两次钱。”
你free(ptr)之后,过了一会儿又(不小心)free(ptr)了一次。这会严重破坏堆的内部管理结构,几乎肯定会导致程序崩溃。

(这就是为什么 free 后设为 NULL 是个好习惯,因为 free(NULL) 是安全的)。
3.4)访问越界
- “租了10平米的仓库,却往里塞了11平米的东西。”
你malloc(10 * sizeof(int)),只申请了10个整数的空间,但你却试图访问my_array[10](这是第11个元素)

二、数据存储的内存结构
🧠 计算机内存中的“数据存储”是什么?
当你写下:int x = 10;这句代码看起来只是一个整数,但程序运行时,它必须存在某个地方才能被 CPU 操作。
这个地方就是 内存(Memory)
- C 语言的数据存储模型主要描述:
- 程序中不同数据存放在 哪一块内存区域
- 这些数据 何时被创建,何时被销毁
- 谁能访问它们(作用域)
🧮 1)内存区的详细特征
1️⃣ 栈(Stack)
-
自动分配与释放(函数调用时开辟,返回时销毁)
-
存放
局部变量、函数参数、返回地址 -
空间较小但速度极快(顺序分配)
-
典型错误:返回栈变量的地址!
int *func() {
int x = 100;
return &x; // ❌ x 是栈变量,函数返回后内存已被回收
}
2️⃣ 堆(Heap)
-
手动分配与释放:
malloc/free -
生命周期: 程序员决定
-
灵活但易出错(内存泄漏、悬空指针)
int *p = malloc(sizeof(int));
*p = 42;
free(p); // ✅ 释放
p = NULL; // ✅ 置空避免野指针
3️⃣ 全局区(静态区 / Data Segment)
-
已初始化区: 如
int a = 10; -
未初始化区(BSS 段): 如
int b;(默认值为0) -
它们在程序启动时由系统分配,程序结束时系统释放。
4️⃣ 常量区(Constant Segment)
-
存放字面常量,如字符串常量
hello -
通常只读,不允许修改
char *s = "hello";
s[0] = 'H'; // ❌ 运行时错误(段错误)
- 注意事项
char s[] = "hello"; // ✅ 拷贝到栈区,可修改
s[0] = 'H';
🧮 2)图片详解

三、总结
动态内存管理是C语言强大灵活性和性能的基石。它允许你构建真正复杂、高效的数据结构(如链表、树、哈希表)
但是也会导致各种安全漏洞



712





