C++内存泄漏检测之编译期 /运行时工具(ASan/Valgrind)

一、编译期 & 运行时工具

1 AddressSanitizer(ASan)

原理

  • 编译器插桩(Clang / GCC)
  • 维护 Shadow Memory
  • 退出时自动检测未释放内存

使用方法

bash 复制代码
# GCC / Clang
-fsanitize=address -g -O1

示例输出

复制代码
==12345==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 128 byte(s) in 1 object(s) allocated from:
    #0 operator new
    #1 foo()

优点

非常快

精确到行号

同时检测越界 / UAF

缺点

程序变慢 ~2x

不能和 MSVC 原生兼容

适合

Linux / macOS

CI / 日常开发

SLAM / 长时间运行程序


2 Valgrind(Memcheck)

原理

  • 虚拟 CPU 执行
  • 跟踪每一条内存读写

使用方法

bash 复制代码
valgrind --leak-check=full ./your_program

示例输出

复制代码
==12345== 32 bytes in 1 blocks are definitely lost

优点

非常全面

无需重新编译

缺点

极慢(10--50x)

不适合实时系统

适合

深度排查顽固泄漏

小规模程序

好,这一段已经是专业级速查表 了,我来在你这个基础上往下"深度展开" ,补齐工程实战里真正会踩的坑和高阶用法 ,尤其偏向 C++ / SLAM / 长时间运行系统


二、ASan / LSan 实用细节

很多人"会用",但没用到位,导致要么漏报、要么误报。


1 ASan ≠ 只查越界,它自带 LSan(泄漏检测)

关键点

  • -fsanitize=address 默认包含 LeakSanitizer
  • 只有程序正常退出时才会报告泄漏
  • abort() / SIGKILL / exit before main end 可能看不到泄漏

正确情况

bash 复制代码
ASAN_OPTIONS=detect_leaks=1:halt_on_error=1 ./your_program

常用 ASAN_OPTIONS(开发中常用)

bash 复制代码
ASAN_OPTIONS=\
detect_leaks=1\
:check_initialization_order=1\
:strict_init_order=1\
:detect_stack_use_after_return=1
含义速解
选项 作用
detect_leaks 开启泄漏检测
strict_init_order 查全局对象初始化顺序 bug(SLAM 常见)
detect_stack_use_after_return 查"函数返回后还用栈变量"
halt_on_error 立刻中断,方便 CI

SLAM 工程强烈建议全开


2 为什么 ASan 有时"查不到泄漏"?

这是个经典误区

常见原因

1. 内存仍被全局对象持有
cpp 复制代码
static std::vector<Foo*> g_cache;
  • 程序退出时仍有指针引用
  • LSan 认为这是"reachable"
  • 不会报泄漏

解决方式:

bash 复制代码
ASAN_OPTIONS=detect_leaks=1:report_reachable=1

2. 自定义 allocator / Eigen / TBB
  • Eigen aligned alloc
  • TBB scalable allocator
  • CUDA / OpenCL pinned memory

可能被当成"外部库持有"

对策:

  • suppressions
  • 或在 shutdown 阶段显式释放

3 ASan + O1 是黄金组合(不是 O0)

bash 复制代码
-fsanitize=address -g -O1

原因

选项 影响
-O0 栈布局怪异,误报多
-O1 最推荐
-O2 有时 inline 影响回溯

CI / 本地调试:统一 O1


4 ASan 在 SLAM 中的典型"秒杀场景"

shared_ptr 循环引用

后端线程提前退出

地图对象生命周期混乱

回调里捕获裸指针

全局静态对象顺序问题

提示 Valgrind 很慢才能定位,ASan 秒出


三、Valgrind 深度用法

Valgrind 的价值在于:
"你以为 ASan 查不到的,它能查到"


1 必须加的参数(否则信息不全)

bash 复制代码
valgrind \
--leak-check=full \
--show-leak-kinds=all \
--track-origins=yes \
--num-callers=30 \
./your_program

关键解释

参数 作用
track-origins 查"未初始化内存来源"
num-callers 深栈回溯(模板地狱必开)

2 Valgrind 泄漏分类

复制代码
definitely lost    真泄漏
indirectly lost    连带泄漏
possibly lost      多为指针算术
still reachable    通常可忽略

重点盯 definitely + indirectly


3 Valgrind 最适合干的三件事

  1. ASan 报不出的 未初始化读取
  2. STL / Eigen / boost 内部错误
  3. 小 demo 精确内存统计

不适合

  • 实时 SLAM
  • 大地图
  • 长时间跑

四、Sanitizer 组合

真正的工程不是"选一个",而是组合使用


ASan + UBSan(强烈推荐)

bash 复制代码
-fsanitize=address,undefined

能抓到:

  • 整数溢出
  • 空指针解引用
  • vptr 错误
  • enum 越界

比单 ASan 强一个量级


2 TSAN(线程杀手)

bash 复制代码
-fsanitize=thread

适合:

  • 后端优化
  • 多线程地图更新
  • 回调竞态

不能和 ASan 同时用

分两个 build


五、实践推荐配置

Debug Sanitizer 构建

cmake 复制代码
set(CMAKE_CXX_FLAGS_DEBUG
    "-O1 -g -fsanitize=address,undefined -fno-omit-frame-pointer")

运行时

bash 复制代码
ASAN_OPTIONS=detect_leaks=1:strict_init_order=1 ./slam_node

六、Eigen 泄漏 & "假泄漏"实战

1 Eigen 看起来在泄漏,其实不是

典型 Valgrind 输出

复制代码
still reachable: 1,024 bytes in 8 blocks

根因

Eigen 内部有:

  • 静态缓存(packet math)
  • 对齐用的全局 allocator
  • lazy 初始化

程序退出时不释放

结论

不是 bug
不要为了消灭它乱 free

判断标准

类型 处理
still reachable 忽略
definitely lost 必查
indirectly lost 八成你自己的锅

2 真·Eigen 泄漏:new + Matrix 裸指针

错误写法(后端优化里常见)

cpp 复制代码
Eigen::Matrix<double, 6, 6>* H =
    new Eigen::Matrix<double, 6, 6>();
  • 优化异常 return
  • 多路径 exit
  • 忘了 delete

ASan 报告

复制代码
Direct leak of 288 byte(s)

正确写法

cpp 复制代码
Eigen::Matrix<double, 6, 6> H;

cpp 复制代码
auto H = std::make_unique<Eigen::Matrix<double,6,6>>();

Eigen 对象 99% 不该手动 new


3 Eigen 对齐导致的"free 崩溃"

症状

  • ASan 报 alloc-dealloc-mismatch
  • 只在 Release / AVX 下崩

错误写法

cpp 复制代码
void* p = malloc(sizeof(Eigen::Vector3d));
auto v = new (p) Eigen::Vector3d();
free(p);  // 

正解

cpp 复制代码
Eigen::aligned_allocator<Eigen::Vector3d>

或干脆别玩 placement new


七、Sophus:泄漏几乎都不是 Sophus 的锅

99% 来自 "值类型被误当指针类型"


1 Sophus 对象被 new

错误示例

cpp 复制代码
Sophus::SE3d* pose = new Sophus::SE3d();

ASan

复制代码
Direct leak of 48 byte(s)

正确方式

cpp 复制代码
Sophus::SE3d pose;

cpp 复制代码
std::unique_ptr<Sophus::SE3d>

Sophus 设计就是 轻量值语义


2 Sophus + std::vector + 对齐陷阱

症状

  • Release 下偶现崩溃
  • ASan 报 heap-buffer-overflow

错误写法

cpp 复制代码
std::vector<Sophus::SE3d> poses;

(在老 Eigen / 开 AVX)

正解

cpp 复制代码
std::vector<Sophus::SE3d,
    Eigen::aligned_allocator<Sophus::SE3d>> poses;

或(Eigen ≥ 3.4)

cpp 复制代码
#define EIGEN_DONT_ALIGN_STATICALLY

3 Sophus"泄漏"但其实是 shared_ptr

经典结构

cpp 复制代码
struct Frame {
    std::shared_ptr<MapPoint> mp;
};

struct MapPoint {
    std::shared_ptr<Frame> ref;
};

ASan:

复制代码
Indirect leak of xxx bytes

正解

cpp 复制代码
std::weak_ptr<Frame> ref;

这类是 SLAM 最大的真实泄漏来源


八、g2o:泄漏重灾区

g2o 本身大量裸指针 + 工厂模式


1 忘记释放 SparseOptimizer

错误模式

cpp 复制代码
auto optimizer = new g2o::SparseOptimizer();
// addVertex / addEdge
return;  // 💀

ASan

复制代码
Indirect leak of 100k+ bytes

正解

cpp 复制代码
g2o::SparseOptimizer optimizer;

栈对象,自动析构


2 Edge / Vertex 是谁来删?

易错点

cpp 复制代码
optimizer.addVertex(v);
optimizer.addEdge(e);

optimizer 析构时才 delete

如果中途 new / delete optimizer:

  • 边 / 点可能残留
  • 产生间接泄漏

生命周期要包住整个 BA


3 Factory 注册导致的"全局泄漏"

Valgrind 报

复制代码
still reachable: g2o::Factory

这是啥?

  • g2o 插件注册
  • 静态 map
  • 进程级对象

可以忽略

不要试图手动清理


4 自定义 Edge / Vertex 忘了虚析构

症状

  • ASan 报小块 definitely lost
  • 只在 delete base pointer 时出现

错误

cpp 复制代码
struct MyEdge : g2o::BaseUnaryEdge<...> {
    ~MyEdge() {}  //  非虚
};

正确

cpp 复制代码
virtual ~MyEdge() = default;

九、组合案例:SLAM 后端典型泄漏链

真实场景

复制代码
Map
 └─ shared_ptr<Frame>
      └─ shared_ptr<g2o::VertexSE3>
           └─ optimizer

optimizer 先析构

Frame 还活着

Vertex 永远删不掉

解决原则(铁律)

g2o 对象只属于 optimizer

  • Frame 不持有 g2o 指针
  • 只保存 ID / index
  • BA 完全结束后整体销毁

十、总结

ASan = 日常开发主力
Valgrind = 最后一根银针
TSAN = 并发问题唯一解

相关推荐
leaves falling2 小时前
c语言-编译和链接
c语言·开发语言
kk5792 小时前
【MATLAB R2018a】路径文件pathdef.m为只读文件无法保存到matlab启动文件夹的问题
开发语言·matlab
黎雁·泠崖2 小时前
Java静态变量底层:内存图解析+避坑指南
java·开发语言
布局呆星2 小时前
魔术方法与魔术变量
开发语言·python
Gary董2 小时前
java死锁
java·开发语言
陳10302 小时前
C++:多态
开发语言·c++
m0_497214152 小时前
qt实现打印机功能
开发语言·qt
Fcy6482 小时前
C++11 新增特性(上)
开发语言·c++·c++11·右值引用和移动语意
@大迁世界2 小时前
Swift、Flutter 还是 React Native:2026 年你该学哪个
开发语言·flutter·react native·ios·swift