目录
[4. 常⻅的动态内存的错误](#4. 常⻅的动态内存的错误)
[4.1 对NULL指针的解引⽤操作](#4.1 对NULL指针的解引⽤操作)
[4.2 对动态开辟空间的越界访问](#4.2 对动态开辟空间的越界访问)
[4.3 对⾮动态开辟内存使⽤free释放](#4.3 对⾮动态开辟内存使⽤free释放)
[4.4 使⽤free释放⼀块动态开辟内存的⼀部分](#4.4 使⽤free释放⼀块动态开辟内存的⼀部分)
[4.5 对同⼀块动态内存多次释放](#4.5 对同⼀块动态内存多次释放)
[4.6 动态开辟内存忘记释放(内存泄漏)](#4.6 动态开辟内存忘记释放(内存泄漏))
[5. 柔性数组](#5. 柔性数组)
[5.1 柔性数组的特点:](#5.1 柔性数组的特点:)
[5.2 柔性数组的使⽤](#5.2 柔性数组的使⽤)
[6. 总结C/C++中程序内存区域划分](#6. 总结C/C++中程序内存区域划分)
4. 常⻅的动态内存的错误
4.1 对NULL指针的解引⽤操作
错误的写法:
cpp
错误的写法
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
int main()
{
int* p = (int*)malloc(INT_MAX);
*p = 20; //如果不去判断p是否为空指针,则会报错
return 0;
}
解析:
错误1:没有检查malloc返回值
问题:
-
malloc(INT_MAX)尝试分配非常大的内存 -
INT_MAX通常是 2147483647(约2GB) -
很可能分配失败,返回
NULL -
如果
p是NULL,*p = 20会导致 程序崩溃
正确的做法:
cpp
int* p = (int*)malloc(INT_MAX);
if (p == NULL)
{
perror("malloc失败");
return 1; // 退出程序
}
*p = 20; // ✅ 安全
问题2 :程序退出时,这2GB内存没有使用free去释放!
1. 内存泄漏的后果
程序开始
↓
malloc申请2GB ← 内存被占用
↓
使用内存
↓
程序结束 ← ❌ 内存没有归还系统!
4.2 对动态开辟空间的越界访问
错误的写法:
cpp
错误的写法
void test()
{
int i = 0;
int* p = (int*)malloc(10 * sizeof(int));
if (NULL == p)
{
exit(EXIT_FAILURE);
}
for (i = 0; i <= 10; i++)
{
*(p + i) = i; //当i=10的时候,会越界访问
}
free(p);
}
int main()
{
test();
return 0;
}
解析:
主要问题:数组越界访问:
cpp
for (i = 0; i <= 10; i++)
{
*(p + i) = i;
}
问题分析:
-
分配了10个int :
malloc(10 * sizeof(int)) -
有效索引:0, 1, 2, 3, 4, 5, 6, 7, 8, 9(共10个)
-
循环条件 :
i <= 10
当 i = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 → 正常
当 i = 10 → ❌ 越界访问
内存示意图:
cpp
分配的内存(10个int = 40字节):
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│ p[0]│ p[1]│ p[2]│ p[3]│ p[4]│ p[5]│ p[6]│ p[7]│ p[8]│ p[9]│
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
↑ ↑ ↑
安全访问 安全访问 ❌ 越界访问
(p+0 到 p+9) (p+10 不存在)
实际内存布局:
cpp
p+0 → 地址1000: 存0
p+1 → 地址1004: 存1
...
p+9 → 地址1036: 存9
p+10 → 地址1040: ❌ 这里不属于你分配的内存!
越界访问的后果:
可能情况1:覆盖其他数据
cpp
// 假设p+10指向另一个变量的内存
*(p + 10) = 10; // 可能修改了其他变量的值
可能情况2:程序崩溃(段错误)
cpp
// 如果p+10指向受保护的内存区域
*(p + 10) = 10; // 触发段错误,程序崩溃
可能情况3:引发安全漏洞
- 缓冲区溢出攻击就是利用这种越界访问
正确的写法:
方法1:使用 < 而不是 <=
cpp
for (i = 0; i < 10; i++) // ✅ 正确:0~9
{
*(p + i) = i;
}
方法2:使用变量名代替硬编码
cpp
int size = 10;
int* p = (int*)malloc(size * sizeof(int));
for (i = 0; i < size; i++) // ✅ 清晰明了
{
p[i] = i; // 也可以用数组表示法
}
**代码的其他注意事项:
- exit(EXIT_FAILURE) 的使用**
cpp
if (NULL == p)
{
exit(EXIT_FAILURE); // 立即终止程序
}
注意 :exit() 会终止整个程序 ,而不仅仅是 test() 函数。
一句话总结:
主要问题是数组越界访问 :malloc 分配了10个int的空间(索引0~9),但循环条件 i <= 10 会访问到第11个元素(索引10),这是非法的内存访问。应该改为 i < 10。
4.3 对⾮动态开辟内存使⽤free释放
错误的写法:
cpp
错误的写法
int main()
{
int a = 10;
int* p = &a;
free(p); //free只能释放动态所开辟的空间,如malloc所开辟的空间就可以使用
p = NULL;
return 0;
}
解析代码:
核心问题:错误的内存释放
cpp
int a = 10; // 栈内存变量
int* p = &a; // p 指向栈内存地址
free(p); // ❌ 尝试释放栈内存
1. 栈内存 (Stack Memory)
cpp
int a = 10; // 栈上分配
特性:
-
自动分配:进入函数时自动分配
-
自动释放:函数结束时自动回收
-
生命周期:与函数作用域绑定
-
管理方式:编译器自动管理
2. 堆内存 (Heap Memory)
cpp
int* p = malloc(sizeof(int)); // 堆上分配
特性:
-
手动分配:需要显式调用分配函数
-
手动释放 :必须显式调用
free() -
生命周期 :直到调用
free()为止 -
管理方式:程序员手动管理
错误原因:
free() 函数内部会:
-
检查指针是否在堆内存范围内
-
检查指针是否指向某个内存块的起始位置
-
栈地址
&a不满足这些条件
正确的写法:
cpp
#include <stdlib.h>
int main() {
int* p = malloc(sizeof(int)); // 在堆上分配
if (p == NULL) return 1;
*p = 10; // 使用
free(p); // ✅ 正确释放
p = NULL; // ✅ 防止野指针
return 0;
}
根本原因总结:
开始的代码的主要问题是混淆了内存管理模型:
-
栈内存模型:自动管理(分配/释放)
-
适用:局部变量、函数参数
-
操作:自动进行,程序员不干预
-
-
堆内存模型:手动管理(分配/释放)
-
适用:动态数据结构、大内存
-
操作:malloc/free,程序员全权负责
-
4.4 使⽤free释放⼀块动态开辟内存的⼀部分
错误的写法:
cpp
错误的写法
int main()
{
int* p = (int*)malloc(100);
if (p == NULL);
{
return 1;
}
int i = 0;
for (i = 0; i < 5; i++)
{
*p = i + 1;
p++;
}
free(p); //此时的p已经不是动态事情的空间的起始位置了,不能进行free
p = NULL;
return 0;
}
解析代码:
主要问题:指针移动导致错误的free
问题分析:
cpp
for (i = 0; i < 5; i++)
{
*p = i + 1;
p++; // ❌ 移动了原始指针!
}
free(p); // ❌ 此时p不是起始地址!
内存变化图示:
cpp
初始状态(malloc后):
p → [???][???][???][???][???]
↑
起始地址 = 0x1000
循环中:
第1次:p[0]=1, p++ → p=0x1004
第2次:p[0]=2, p++ → p=0x1008
第3次:p[0]=3, p++ → p=0x100C
第4次:p[0]=4, p++ → p=0x1010
第5次:p[0]=5, p++ → p=0x1014
结束循环后:
p → [ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][???]...
↑
p现在指向这里(0x1014)
不是起始地址!
结果 :free(p) 试图释放一个不是malloc返回的地址,导致崩溃。
正确的写法:
cpp
int main()
{
int* p = (int*)malloc(100);
if (p == NULL) // 去掉分号
{
return 1;
}
int i = 0;
for (i = 0; i < 5; i++)
{
*(p + i) = i + 1; // ✅ 不移动p,用p+i访问
// 或者:p[i] = i + 1; // 这样写更清楚
}
free(p); // ✅ p还是起始位置
p = NULL;
return 0;
}
4.5 对同⼀块动态内存多次释放
错误的写法:
cpp
void test()
{
int* p = (int*)malloc(100);
free(p); //如果它们之间加了 p=NULL 可以避免重复释放导致的报错
free(p); //重复释放,导致报错
}
int main()
{
test();
return 0;
}
解析代码:
问题分析:重复释放
内存状态变化:
cpp
步骤1:malloc(100)
p → [分配的100字节内存块] ← 状态:已分配
步骤2:第一次 free(p)
p → [同一个内存块] ← 状态:已释放(还给系统)
步骤3:第二次 free(p)
p → [同一个内存块] ← 状态:❌ 已经是释放状态!
再次释放 → 崩溃
为什么会崩溃?
free() 的内部工作:
-
检查指针是否有效
-
查找内存管理记录
-
第一次free:标记为"可用"
-
第二次free:发现已经是"可用"状态
-
报错:
double free or corruption
正确的写法:
解决方案:释放后立即置NULL
cpp
void test()
{
int* p = (int*)malloc(100);
free(p); // 第一次释放
p = NULL; // ✅ 立即置NULL
free(p); // ✅ free(NULL) 是安全的,什么都不做
}
4.6 动态开辟内存忘记释放(内存泄漏)
错误的写法:
cpp
void test()
{
int* p = (int*)malloc(100);
if (NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while (1); //一直循环,没有free去释放空间,导致内存泄漏
}
解析代码:
主要问题:内存泄漏
cpp
void test()
{
int* p = (int*)malloc(100);
if (NULL != p)
{
*p = 20;
}
// ❌ 没有 free(p)!
// p 是局部变量,函数结束时会销毁
// 但 malloc 分配的 100 字节内存还在!
}
内存泄漏图示:
cpp
调用 test() 时:
1. malloc(100) → 从堆分配100字节
2. p 指向这块内存
3. *p = 20 使用内存
4. 函数结束,p 被销毁(栈回收)
5. 但堆上的100字节还在占用! ← ❌ 内存泄漏
6. 没有指针指向这块内存了,无法释放
正确的写法:
cpp
#include <stdlib.h>
void test()
{
int* p = (int*)malloc(100);
if (NULL != p)
{
*p = 20;
free(p); // ✅ 使用后立即释放
p = NULL;
}
}
int main()
{
test();
return 0;
}
内存泄漏的后果:
长期运行程序
cpp
时间线:
0秒:程序启动
10秒:test()调用100次 → 泄漏10KB
1小时:test()调用10000次 → 泄漏1MB
1天:test()调用百万次 → 泄漏100MB
几天后:内存耗尽,程序崩溃
5. 柔性数组
也许你从来没有听说过柔性数组(flexiblearray)这个概念,但是它确实是存在的。 C99中,结构中的最后⼀个元素允许是未知⼤⼩的数组,这就叫做『柔性数组』成员。
例如:
cpp
struct a
{
int i;
char n;
int a[]; //最后一个成员变量为数组,而且没有定义大小,此时为柔性数组
// int a[0]; //也可以加个0进去,是一样的
};
5.1 柔性数组的特点:
- 结构中的柔性数组成员前⾯必须⾄少⼀个其他成员。
- sizeof返回的这种结构⼤⼩不包括柔性数组的内存。
- 包含柔性数组成员的结构⽤malloc()函数进⾏内存的动态分配,并且分配的内存应该⼤于结构的⼤ ⼩,以适应柔性数组的预期⼤⼩。
例如:
cpp
struct S
{
int a; //占4个字节
int arr[]; //不知道占多少个字节
};
int main()
{
printf("%zd\n", sizeof(struct S)); //输出4
return 0;
}
输出结果:

5.2 柔性数组的使⽤
cpp
struct S
{
int n; //4个字节
int arr[];
};
int main()
{
struct S* ps = (struct S*)malloc(sizeof(struct S) + 5 * sizeof(int));
if (ps == NULL)
{
perror("malloc");
return 1;
}
ps->n = 100;
int i = 0;
for (i = 0; i < 5; i++)
{
ps->arr[i] = i;
}
//调整空间
struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 10 * sizeof(int));
if (ptr != NULL)
{
ps = ptr;
}
//释放空间
free(ps);
ps = NULL;
return 0;
}
代码解析:
第一步:定义柔性数组结构体
cpp
struct S
{
int n; // 4个字节
int arr[]; // 柔性数组,大小未知
};
知识点:柔性数组
-
int arr[]是柔性数组成员 -
它必须是结构体的最后一个成员
-
不占用结构体大小 ,
sizeof(struct S)只包含n的大小 -
用于动态分配结构体+数组的连续内存
第二步:分配内存
cpp
struct S* ps = (struct S*)malloc(sizeof(struct S) + 5 * sizeof(int));
内存分配解析:
-
sizeof(struct S)= 4字节(存储n) -
5 * sizeof(int)= 5 × 4 = 20字节(存储arr数组) -
总大小 = 4 + 20 = 24字节
-
内存布局 :结构体和数组在连续的内存中
内存示意图:
cpp
分配的内存块(24字节):
┌──────────────┬────────────────────────────┐
│ 结构体部分 │ 柔性数组部分 │
│ n (4字节) │ arr[0]-arr[4] (20字节) │
└──────────────┴────────────────────────────┘
ps→↑
第三步:使用内存
cpp
ps->n = 100; // 设置结构体成员
int i = 0;
for (i = 0; i < 5; i++)
{
ps->arr[i] = i; // 设置数组元素
}
内存状态变化:
cpp
执行前: [随机值][随机值][随机值][随机值][随机值]
执行后: [ 100 ][ 0 ][ 1 ][ 2 ][ 3 ][ 4 ]
↑n arr[0] arr[1] arr[2] arr[3] arr[4]
知识点:箭头运算符
-
ps->n等价于(*ps).n -
用于通过指针访问结构体成员
第四步:调整空间(扩容)
cpp
struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 10 * sizeof(int));
realloc解析:
-
原大小:
sizeof(struct S) + 5 * sizeof(int)= 4 + 20 = 24字节 -
新大小:
sizeof(struct S) + 10 * sizeof(int)= 4 + 40 = 44字节 -
扩容20字节(增加5个int的空间)
realloc的两种可能:
-
原地扩容 :后面有足够空间,
ptr == ps -
移动扩容 :分配新内存,复制数据,释放旧内存,
ptr != ps
检查realloc结果
cpp
if (ptr != NULL)
{
ps = ptr; // 更新指针
}
知识点:安全更新指针
-
不直接
ps = realloc(ps, ...),防止失败时丢失原指针 -
用临时变量
ptr接收结果 -
成功时才更新
ps
内存扩容后:
cpp
扩容前(24字节): [100][0][1][2][3][4]
扩容后(44字节): [100][0][1][2][3][4][?][?][?][?][?]
↑ 新增的5个int空间
6. 总结C/C++中程序内存区域划分
C/C++程序内存分配的⼏个区域:
-
栈区(stack):在执⾏函数时,函数内局部变量的存储单元都可以在栈上创建,函数执⾏结束时 这些存储单元⾃动被释放。栈内存分配运算内置于处理器的指令集中,效率很⾼,但是分配的内 存容量有限。栈区主要存放运⾏函数⽽分配的局部变量、函数参数、返回数据、返回地址等。 《函数栈帧的创建和销毁》
-
堆区(heap):⼀般由程序员分配释放,若程序员不释放,程序结束时可能由OS(操作系统) 回收。分配⽅式类似于链表。
-
数据段(静态区):(static)存放全局变量、静态数据。程序结束后由系统释放。
-
代码段:存放函数体(类成员函数和全局函数)的⼆进制代码

以上就是我们动态内存管理的全部内容了,谢谢大家!!!!!!