Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← C++ 编程 / C++ Programming

内存管理 / Memory Management

1. C++ 内存管理总览:先看完整地图 / C++ Memory Management Map

2. 程序内存布局:内存不是只有堆和栈 / Program Memory Layout

3. 栈与函数调用:局部变量为什么会消失 / Stack and Function Calls

4. 堆与动态分配:new 到底做了什么 / Heap and Dynamic Allocation

5. 对象生命周期:地址还在不代表对象还活着 / Object Lifetime

6. RAII 与所有权:让对象负责清理 / RAII and Ownership Design

7. 智能指针与所有权关系:谁拥有,谁只是看 / Smart Pointers and Ownership Graphs

8. 容器、迭代器与内存失效 / Containers, Iterators, and Invalidation

9. allocator 与 PMR:把分配策略抽出来 / Allocators and PMR

10. 内存错误与 Sanitizer:把崩溃变成证据 / Memory Errors and Sanitizers

本页目录

内存错误与 Sanitizer:把崩溃变成证据 / Memory Errors and Sanitizers ​

内存错误最烦人的地方是:它不一定立刻崩溃。

你需要工具把“可能哪里坏了”变成“哪一行访问了哪块已经不合法的内存”。

1. 未定义行为为什么危险 ​

C++ 里很多内存错误属于未定义行为。

未定义行为不是“结果随机”这么简单。

它的意思是:

text
程序已经违反语言规则。
C++ 标准不再保证后续行为。
1
2

可能结果包括:

text
立刻崩溃
输出看似正确
调试版正常,发布版错误
换编译器后错误
优化级别一变就错误
很久以后在别处崩溃
1
2
3
4
5
6

所以不要用“我这里跑起来没事”证明内存代码正确。

2. 常见错误一:数组越界 ​

cpp
int main() {
    int values[3]{1, 2, 3};
    return values[3];
}
1
2
3
4

有效下标是:

text
0, 1, 2
1

values[3] 越界。

栈上大致是:

text
+---------+---------+---------+---------+
| values0 | values1 | values2 | other   |
+---------+---------+---------+---------+
                              ^
                              |
                           values[3]
1
2
3
4
5
6

你读到的可能是别的局部数据、填充字节,也可能触发工具报警。

3. 常见错误二:释放后使用 ​

cpp
int main() {
    int* p = new int(42);
    delete p;
    return *p;
}
1
2
3
4
5

过程:

text
new int
  |
  v
p points to live int
  |
  v
delete p
  |
  v
int lifetime ended, storage released
  |
  v
*p tries to read old location
1
2
3
4
5
6
7
8
9
10
11
12
13

这叫 use-after-free。

p 不是空不代表它安全。

4. 常见错误三:栈对象失效 ​

cpp
int* bad() {
    int x = 42;
    return &x;
}

int main() {
    int* p = bad();
    return *p;
}
1
2
3
4
5
6
7
8
9

这里是 stack-use-after-return 或类似问题。

x 在 bad() 调用帧里。

函数返回后,调用帧撤掉。

p 保存旧地址,但对象已经没了。

5. 常见错误四:泄漏 ​

cpp
void leak() {
    int* p = new int(42);
}
1
2
3

函数返回后,p 这个局部指针变量销毁。

但它指向的堆对象没有 delete。

结果是:

text
指向对象的最后地址入口没了
对象还占着动态存储
程序无法再释放它
1
2
3

这就是内存泄漏。

更好的写法:

cpp
#include <memory>

void no_leak() {
    auto p = std::make_unique<int>(42);
}
1
2
3
4
5

p 离开作用域时自动释放。

6. 常见错误五:重复释放 ​

cpp
int* p = new int(42);
delete p;
delete p;
1
2
3

第二次 delete 时,那块对象生命周期已经结束。

分配器内部状态可能已经改变。

重复释放可能破坏堆管理结构。

这类错误经常比普通崩溃更危险。

7. 用 Sanitizer 编译 ​

可以用:

bash
g++ -std=c++17 -g -O1 -fsanitize=address,undefined -fno-omit-frame-pointer main.cpp -o app
1

逐项解释:

text
g++
  调用 C++ 编译器

-std=c++17
  使用 C++17 语言规则

-g
  生成调试信息,方便报告显示源码行

-O1
  开一点优化,Sanitizer 通常能较好工作

-fsanitize=address
  打开 AddressSanitizer,检查很多内存访问错误

-fsanitize=undefined
  打开 UndefinedBehaviorSanitizer,检查部分未定义行为

-fno-omit-frame-pointer
  保留帧指针,让调用栈更清楚

main.cpp
  输入源文件

-o app
  输出可执行文件 app
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

运行:

bash
./app
1

Windows 环境可能是:

powershell
.\app.exe
1

8. 怎样读 ASan 报告 ​

ASan 报告通常会告诉你:

text
错误类型
出错地址
读还是写
访问大小
发生在哪个源文件哪一行
调用栈
相关内存之前在哪里分配或释放
1
2
3
4
5
6
7

读报告时不要被大段输出吓住。

先找:

text
ERROR: AddressSanitizer: heap-use-after-free
1

这行说明错误类别。

再找你的源码文件名和行号。

然后看调用栈:

text
main
  |
  v
bad_function
  |
  v
实际触发错误的位置
1
2
3
4
5
6
7

目标是找“第一次违反生命周期或边界规则的地方”,不是只盯着最后崩溃点。

9. 工具不是证明器 ​

Sanitizer 很有用,但它不能证明程序没有 bug。

原因是:

text
它只能检查它支持的错误类型。
它只能检查本次运行实际走到的路径。
某些平台或编译器支持不完整。
某些未定义行为仍然可能漏掉。
1
2
3
4

所以工具要和这些方法一起用:

text
编译警告
单元测试
代码审查
接口所有权设计
最小复现
调试器
1
2
3
4
5
6

10. 本篇总结 ​

常见内存错误可以归类:

text
out-of-bounds
  访问范围外元素

use-after-free
  动态对象释放后继续使用

use-after-scope / use-after-return
  栈对象生命周期结束后继续使用

leak
  失去释放资源的入口

double free
  同一资源释放两次

iterator invalidation
  容器搬家后继续使用旧位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

排查顺序:

text
固定编译命令
  |
  v
打开 warning 和 sanitizer
  |
  v
固定输入,稳定复现
  |
  v
读错误类型和源码行
  |
  v
回到生命周期、所有权、边界三个问题
1
2
3
4
5
6
7
8
9
10
11
12
13

内存管理学到最后,不是靠胆子大。

是靠清楚的所有权设计和可验证的错误证据。

最后更新于:

Pager
上一篇9. allocator 与 PMR:把分配策略抽出来 / Allocators and PMR

持续记录,持续成长

Copyright © Tidenflow