C++ 内存泄漏排查:从裸 new 到 RAII 修复,附 ASan 实战

C++ 内存泄漏排查:从裸 new 到 RAII 修复,附 ASan 实战

背景

机器人感知节点里,每帧点云、IMU 数据都要分配缓冲。

如果用了裸 new 但忘了 delete,跑几小时内存就持续上涨。

最终节点被 OOM 杀掉,线上排查只能看 RSS 曲线。

本文用一个最小例子,演示泄漏怎么产生、怎么用 ASan 定位、怎么用现代 C++ 修掉。

反面教材:最经典的泄漏

cpp 复制代码
#include <iostream>
#include <string>

void ProcessPointCloud(const std::string& sensor_id) {
    // 预处理缓冲:运行时才知道大小,必须 new
    int* buffer = new int[1024];

    // 模拟处理
    buffer[0] = 42;
    std::cout << "  [" << sensor_id << "] 处理完成" << std::endl;

    // 忘记 delete[] buffer;  <-- 泄漏点
}

int main() {
    std::cout << "=== 触发内存泄漏 ===" << std::endl;
    for (int i = 0; i < 3; ++i) {
        ProcessPointCloud("Lidar_" + std::to_string(i));
    }
    std::cout << "程序结束,ASan 会报告泄漏详情。" << std::endl;
    return 0;
}

编译运行:

bash 复制代码
g++ -std=c++17 -fsanitize=address -g exercise2_leak.cpp -o exercise2_leak && ./exercise2_leak

ASan 会直接打印泄漏报告,定位到 new int[1024] 那一行。

修复版:两种现代写法

cpp 复制代码
#include <iostream>
#include <string>
#include <vector>
#include <memory>

// 修复方式一:用 vector 管理动态数组
void ProcessPointCloudVector(const std::string& sensor_id) {
    std::vector<int> buffer(1024);
    buffer[0] = 42;
    std::cout << "  [" << sensor_id << "] vector 版处理完成" << std::endl;
}

// 修复方式二:用 unique_ptr 管理动态数组
void ProcessPointCloudUnique(const std::string& sensor_id) {
    auto buffer = std::make_unique<int[]>(1024);
    buffer[0] = 42;
    std::cout << "  [" << sensor_id << "] unique_ptr 版处理完成" << std::endl;
}

int main() {
    std::cout << "=== 修复后:无泄漏 ===" << std::endl;
    for (int i = 0; i < 3; ++i) {
        ProcessPointCloudVector("Lidar_" + std::to_string(i));
    }
    for (int i = 0; i < 3; ++i) {
        ProcessPointCloudUnique("Lidar_" + std::to_string(i));
    }
    std::cout << "程序结束,ASan 报告干净。" << std::endl;
    return 0;
}

编译运行:

bash 复制代码
g++ -std=c++17 -fsanitize=address -g exercise2_fixed.cpp -o exercise2_fixed && ./exercise2_fixed

跑完 ASan 报告是干净的,零泄漏。

现代 C++ 内存管理规则

场景 写法
普通对象 std::make_unique<T>() / std::make_shared<T>()
动态数组 std::vector<T>
独占资源 std::unique_ptr<T>
共享资源 std::shared_ptr<T>
观察资源 std::weak_ptr<T>
对象池 / 对齐内存 内部用 placement new,外层 RAII
与 C 接口交互 裸指针进,RAII 包装出

结论

业务层不写裸 new/delete。

底层或性能敏感场景会用,但必须被 RAII 包起来。

排查流程:复现 → 加 -fsanitize=address 重编 → 跑一遍看报告 → 修掉 → 再跑一遍确认归零。

相关推荐
爱笑的k1130 分钟前
nano-vllm 源码剖析:千行 Python 手写离线推理引擎的调度、显存与并行时序
开发语言·python·vllm
X1A0RAN2 小时前
PPT 图片批量处理教程:导出全部幻灯片 · 提取全部素材
开发语言·javascript·powerpoint
熊猫钓鱼>_>3 小时前
普通用户视角的端侧智能体验手记【鸿蒙心迹】
开发语言·人工智能·华为·智能手机·性能优化·ai编程·harmonyos
瑞雪兆丰年兮3 小时前
JavaWeb-day13-SpringBootWeb事务管理&AOP
java·开发语言·springboot·javaweb·事务·web·aop
布莱克6055 小时前
Swift 常量详解:从 let 到编译期优化
服务器·开发语言·ssh·swift·常量·let
布莱克6055 小时前
Swift 可选类型(Optionals)详解:从原理到实战
开发语言·安全·ssh·swift·optional·可选类型
老歌老听老掉牙5 小时前
案例 7:二维 Rosenbrock 函数最小值(香蕉函数)
c++·pso
Severus_black6 小时前
【C++初阶】Stack、Queue算法题大练兵!
c语言·c++·算法·stl
瑞雪兆丰年兮7 小时前
JavaWeb-day14-SpringBootWeb原理篇
java·开发语言·spring boot·spring·bean·mybatis·javaweb
SomeOtherTime8 小时前
Rust生命周期介绍(AI回答)
开发语言·后端·rust