0

0

c++如何实现简单的内存泄漏排查_c++ 重载new与delete运算符记录【指南】

穿越時空

穿越時空

发布时间:2025-12-27 14:19:02

|

844人浏览过

|

来源于php中文网

原创

重载 new 和 delete 是排查内存泄漏最直接的手段,因其可在每次分配/释放时插入日志、堆栈捕获或计数逻辑,无需修改业务代码且不依赖外部工具;但须同时覆盖全局及数组版本,并妥善处理 size 获取、线程安全、递归调用与 STL 绕过等问题。

c++如何实现简单的内存泄漏排查_c++ 重载new与delete运算符记录【指南】

为什么重载 newdelete 是排查内存泄漏最直接的手段

因为 C++ 标准分配器不记录谁在什么位置申请了多大块内存,而重载全局 new/delete 能在每次分配/释放时插入日志、堆捕获或计数逻辑——不需要修改业务代码,也不依赖外部工具(如 Valgrind 或 ASan),适合嵌入式、游戏引擎或无法用调试器的环境。

但要注意:仅重载全局版本(::operator new / ::operator delete)不够,必须同时覆盖数组版本(operator new[] / operator delete[]),否则 new int[10] 类操作会绕过你的监控。

如何安全地重载全局 newdelete 并记录调用点

核心是用 __FILE____LINE__backtrace()(Linux)或 CaptureStackBackTrace(Windows)获取上下文。为避免递归调用(比如日志本身 malloc),所有记录逻辑必须使用栈内存或预分配缓冲区。

  • 重载函数必须声明在全局作用域,且不能在头文件中重复定义(加 #pragma onceinline 修饰符易出错,建议单独实现于一个 .cpp 文件)
  • 不要在重载函数里调用 std::coutmallocnew 或任何可能间接触发分配的函数
  • 用静态数组缓存调用栈地址,再用 dladdr(Linux)或 SymFromAddr(Windows)做符号解析;若不想依赖符号表,至少保留 __FILE____LINE__
  • 用原子计数器(std::atomic_size_t)统计当前未释放字节数,避免多线程竞争
void* operator new(size_t size) {
    void* ptr = malloc(size);
    if (ptr) {
        static std::atomic_size_t total_allocated{0};
        total_allocated += size;
        fprintf(stderr, "[ALLOC] %p %zu bytes at %s:%d\n", ptr, size, __FILE__, __LINE__);
    }
    return ptr;
}

void operator delete(void* ptr) noexcept { if (ptr) { static std::atomic_size_t total_allocated{0}; // 这里无法知道 size —— 实际需配合 malloc_usable_size 或自建映射表 fprintf(stderr, "[FREE] %p\n", ptr); free(ptr); } }

如何补全 size 信息并支持匹配检查

标准 operator delete 不带 size 参数,所以单纯打印 __FILE__/__LINE__ 无法判断哪次 new 没被配对释放。解决方法是维护一张哈希表:以指针为 key,存 size + 分配位置 + 时间戳。

Looka
Looka

AI辅助Logo和品牌设计工具

下载

立即学习C++免费学习笔记(深入)”;

  • 哈希表本身必须用 mmap 分配(避免用 new 初始化自己),或用固定大小环形缓冲区 + 线性查找(牺牲精度换安全性)
  • Linux 下可用 malloc_usable_size(ptr) 近似还原 size(仅对 malloc 系分配有效,不适用于自定义对齐或 operator new(std::align_val_t)
  • 启用 C++17 的 operator new(size_t, std::align_val_t) 重载时,必须同步实现对应 delete 版本,否则对齐分配会 fallback 到默认 new,漏监控
  • 程序退出前遍历未释放项,按 size 排序输出 top N 泄漏点,比单纯计数更易定位问题模块

实际使用时最容易忽略的三个坑

很多实现跑起来没报错,但根本抓不到泄漏——不是逻辑错,而是被编译器或运行时绕过了。

  • std::vectorstd::string 等 STL 容器默认使用 std::allocator,它内部可能直接调用 malloc 而非 operator new;要彻底监控,得传入自定义 allocator 或用链接期替换(如 LD_PRELOAD)
  • 静态对象构造期间的 new 可能发生在你的重载函数初始化之前,导致首几笔分配丢失;把记录结构体定义为 static 全局变量而非局部 static,可确保优先构造
  • Release 模式下编译器可能内联或优化掉 __FILE__/__LINE__,或把 backtrace 调用整个删掉;务必在 Release 构建中保留 debug info(-g)并禁用相关优化(如 -fno-omit-frame-pointer

真正有效的内存泄漏定位,从来不是靠“有没有重载”,而是看是否覆盖了所有分配路径、能否还原真实调用上下文、以及是否能在目标环境下稳定复现——其余都是细节。

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

311

2023.08.02

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1428

2023.10.24

Go语言中的运算符有哪些
Go语言中的运算符有哪些

Go语言中的运算符有:1、加法运算符;2、减法运算符;3、乘法运算符;4、除法运算符;5、取余运算符;6、比较运算符;7、位运算符;8、按位与运算符;9、按位或运算符;10、按位异或运算符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

221

2024.02.23

php三元运算符用法
php三元运算符用法

本专题整合了php三元运算符相关教程,阅读专题下面的文章了解更多详细内容。

69

2025.10.17

全局变量怎么定义
全局变量怎么定义

本专题整合了全局变量相关内容,阅读专题下面的文章了解更多详细内容。

70

2025.09.18

python 全局变量
python 全局变量

本专题整合了python中全局变量定义相关教程,阅读专题下面的文章了解更多详细内容。

91

2025.09.18

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

193

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

184

2025.07.04

ip地址修改教程大全
ip地址修改教程大全

本专题整合了ip地址修改教程大全,阅读下面的文章自行寻找合适的解决教程。

27

2025.12.26

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PostgreSQL 教程
PostgreSQL 教程

共48课时 | 6.1万人学习

Git 教程
Git 教程

共21课时 | 2.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号