深入解析:C++ 内存泄漏检测器设计

news/2025/10/7 8:15:22/文章来源:https://www.cnblogs.com/tlnshuju/p/19128163

深入解析:C++ 内存泄漏检测器设计

2025-10-07 08:11  tlnshuju  阅读(0)  评论(0)    收藏  举报


在这里插入图片描述


1. C++中的动态内存分配

动态内存分配不同于固定大小的静态数组,通过 new 和 delete让我们可以根据需要随时申请和释放内存。

先来看段代码:

#
include <iostream>usingnamespace std;int main() {// 动态分配单个整型变量int* pInt =newint;*pInt = 20;cout <<"动态分配的整数数值为: " <<*pInt << endl;// 释放内存delete pInt;// 动态分配数组int arraySize;cout <<"请指定数组的大小: ";cin >> arraySize;int* dynamicArray =newint[arraySize];for (int i = 0; i < arraySize; i++) {dynamicArray[i] = i * 3;}cout <<"数组中的元素为: ";for (int i = 0; i < arraySize; i++) {cout << dynamicArray[i] <<" ";}cout << endl;// 释放数组内存delete[] dynamicArray;return 0;}

上面的代码,做了如下事情:

动态分配单个整型变量:

  1. 内存分配:new int在堆上分配 int 类型的内存。
  2. 指针操作:通过*pInt解引用赋值和读取。
  3. 内存释放:使用delete释放单个对象内存。

动态分配数组:

  1. 内存分配:new int[arraySize]分配连续内存块。
  2. 指针类型:dynamicArray指向数组首元素(类型为int*)。
  3. 数组释放:使用delete[]释放数组内存。

注意事项:无论是单个变量还是数组,动态分配的内存都需要通过 delete 或 delete[] 进行释放。

2. 什么是内存泄漏

内存泄漏是指程序在运行过程中申请了内存空间却未及时释放,导致随着程序运行时间增长,占用的内存持续累积,最终可能耗尽系统可用资源。在 C++ 编程中,避免内存泄漏需牢记以下要点:

  1. 内存释放的配对原则:
    每次通过 new 分配的单个内存对象,必须使用 delete 进行释放;对于通过 new[] 动态分配的数组内存,则需用 delete[] 释放。
  2. 系统不会自动回收动态内存:
    与栈内存不同,堆内存的释放完全依赖开发者手动操作。若忘记释放已申请的动态内存,系统无法自动清理这些 “闲置” 内存,它们会一直驻留在内存中,形成内存泄漏。所以在动态内存使用完毕后,应立即执行释放操作。

3. 内存泄漏的代码案例

1. 基础堆内存未释放(Basic Heap Leak):

new分配的内存未通过delete释放。

int* p =
new
int
;
// 未释放

2. 数组未正确释放(Array Deallocation Mismatch):

new[]分配的数组必须用delete[]释放。若使用delete而非delete[],仅释放首元素内存,其余元素内存泄漏。

int* arr =
new
int[10]
;
// delete arr; // 错误写法
// 正确应使用 delete[] arr;

3. 异常路径未释放(Exception Safety Issue)

异常抛出后,delete语句被跳过,内存未释放。

int* data =
new
int
;
throw std::runtime_error("Oops!"
)
;
// 异常导致delete跳过
delete data;
// 永远不会执行

4. 类成员未释放(Class Member Leak)

动态分配的成员变量buf未在析构函数中释放。当对象销毁时,buf指向的内存仍被占用。

class LeakyClass {
public:
LeakyClass(
) : buf(
new
char[1024]
) {
}
~LeakyClass(
) {
} // 析构函数未释放buf
private:
char* buf;
}
;

5. 容器指针未清理(Container of Pointers)

vector存储的是原始指针,容器销毁时不会自动释放指针指向的内存。

std::vector<
int*> vec;
for (
int i = 0
; i <
5
;
++i) {
vec.push_back(
new
int(i)
)
;
// 未释放元素
}

4. 内存泄漏检查器的设计

原理:通过重载 new/new[] 和 delete/delete[] 运算符,在内存分配/释放时插入跟踪逻辑,在内存分配时:记录内存地址、大小、分配位置(FILELINE),在内存释放时:从跟踪表中移除记录。

模块1:位置信息捕获:

通过预处理器宏替换,在每次内存分配时自动捕获源代码位置信息:文件名和行号。

关键技术点:

  1. 利用__FILE__和__LINE__预定义宏获取当前位置。
  2. 通过宏替换将普通new操作转换为带位置信息的版本。
  3. 避免在实现文件中应用宏替换:防止递归定义。
// mem_leak_detector.hpp
#
ifndef MEM_LEAK_DETECTOR_IMPLEMENTATION
// 关键替换:所有new操作被转换为带位置信息的版本
#
define new new(__FILE__
, __LINE__
)
#
endif
// 位置感知的内存分配运算符声明
void*
operator
new(size_t size,
const
char* file,
int line)
;
void*
operator
new[](size_t size,
const
char* file,
int line)
;

模块2:内存分配跟踪:

重载全局内存分配函数,在分配时记录内存信息。

关键技术点:

  1. 重载operator new和operator new[]捕获分配请求。
  2. 使用malloc进行实际内存分配。
  3. 分配成功后记录指针、大小和位置信息。
  4. 处理分配失败情况返回nullptr。
// mem_leak_detector.cpp
// 重载的全局new运算符
void*
operator
new(size_t size,
const
char* file,
int line) {
void* p = malloc(size)
;
// 实际内存分配
if (p) {
// 记录分配信息:指针、大小、文件名、行号
MemLeakDetector::track(p, size, file, line)
;
}
return p;
}
// 数组版本转发给普通new
void*
operator
new[](size_t size,
const
char* file,
int line) {
return
operator
new(size, file, line)
;
}
// 跟踪函数实现
void MemLeakDetector::track(
void* p, size_t size,
const
char* file,
int line) {
char* file_copy = strdup(file)
;
// 复制文件名(确保长期有效)
allocations[p] = MemAllocRecord{
p, size, file_copy, line
}
;
}

模块3:内存释放跟踪:

重载全局内存释放函数,在释放时移除跟踪记录。

关键技术点:

  1. 重载operator delete和operator delete[]。
  2. 释放前从跟踪表中移除记录。
  3. 使用free进行实际内存释放。
// mem_leak_detector.cpp
// 重载的全局delete运算符
void
operator
delete(
void* p)
noexcept {
MemLeakDetector::untrack(p)
;
// 从跟踪表中移除
free(p)
;
// 实际内存释放
}
// 数组版本转发给普通delete
void
operator
delete[](
void* p)
noexcept {
operator
delete(p)
;
}
// 停止跟踪函数实现
void MemLeakDetector::untrack(
void* p) {
auto it = allocations.find(p)
;
if (it != allocations.end(
)
) {
free((
void*
)it->second.file)
;
// 释放复制的文件名
allocations.erase(it)
;
// 从映射表移除
}
}

模块4:泄漏记录存储:

使用全局数据结构存储所有未释放的内存分配记录。

关键技术点:

  1. 静态std::map存储分配记录—键:内存地址,值:分配信息。
  2. 使用strdup复制文件名,确保长期有效性。
  3. 在释放时清理复制的文件名。
// mem_leak_detector.hpp
// 内存分配记录结构
struct MemAllocRecord {
void* ptr;
// 内存地址
size_t size;
// 分配大小
const
char* file;
// 分配位置文件名
int line;
// 分配位置行号
}
;
// 静态存储定义
class MemLeakDetector {
private:
static std::map<
void*
, MemAllocRecord> allocations;
// 未释放内存记录表
// ...
}
;

模块5:报告生成:

程序退出时分析未释放记录,生成详细泄漏报告。

关键技术点:

  1. 通过atexit注册报告函数。
  2. 遍历所有未释放记录。
  3. 计算总泄漏字节数。
  4. 分类显示泄漏位置信息。
  5. 区分"无泄漏"和"有泄漏"情况。
// mem_leak_detector.cpp
// 生成泄漏报告:程序退出时自动调用
void MemLeakDetector::report(
) {
if (allocations.empty(
)
) {
std::cout <<
"\n[SUCCESS] No memory leaks detected\n"
;
}
else {
std::cout <<
"\n[MEMORY LEAKS] " << allocations.size(
)
<<
" leaks found:\n"
;
size_t total = 0
;
// 遍历所有未释放的记录
for (
const
auto& entry : allocations) {
const MemAllocRecord& record = entry.second;
std::cout <<
" Leak at " << record.ptr
<<
" (" << record.size <<
" bytes)"
<<
" allocated in " << record.file
<<
":" << record.line <<
"\n"
;
total += record.size;
// 累计泄漏字节数
}
std::cout <<
"Total leaked: " << total <<
" bytes\n"
;
}
}
// 初始化宏注册报告函数
#
define MEM_LEAK_DETECTOR_INIT(
) \
std::atexit(MemLeakDetector::report)

5. 测试案例

测试案例放在main.cpp文件中,内存泄漏检测文件由mem_leak_detector.cpp和mem_leak_detector.hpp组成,我使用的lab环境为 Ubuntu 系统。

执行以下命令看到结果:

g++ -std=c++11 -g mem_leak_detector.cpp main.cpp -o memtest
./memtest

在这里插入图片描述

main.cpp

#
include <vector>#include <stdexcept>#include "mem_leak_detector.hpp" // 引入内存泄漏检测器头文件// 基础内存泄漏示例:分配单个int未释放void basic_leak() {int* p =newint;// 分配内存 (通过重载的new记录位置)}// 数组内存泄漏示例:分配int数组未释放void array_leak() {int* arr =newint[10];// 分配数组 (通过重载的new[]记录)}// 异常导致的内存泄漏:分配后抛出异常跳过deletevoid exception_leak() {int* data =newint;throw std::runtime_error("Oops!");// 抛出异常delete data;// 此句不会执行}// 类内泄漏示例:析构函数未释放成员指针class LeakyClass {public:LeakyClass() : buf(newchar[1024]) {} // 分配内存~LeakyClass() {} // 析构函数未释放buf → 泄漏private:char* buf;};// 容器内存泄漏示例:vector存储指针未释放void container_leak() {std::vector<int*> vec;for (int i = 0; i <5;++i) {vec.push_back(newint(i));// 5次分配} // vector销毁时不会自动释放指针 → 5处泄漏}int main() {MEM_LEAK_DETECTOR_INIT();// 注册退出时报告泄漏basic_leak();// 产生1处泄漏array_leak();// 产生1处泄漏 (数组)try {exception_leak();// 抛出异常导致1处泄漏}catch (...) {} // 捕获异常但不处理泄漏LeakyClass* cls =new LeakyClass();// 类内泄漏 + 对象本身泄漏 → 共2处container_leak();// 产生5处泄漏// 注意:未释放cls指针 → 额外泄漏LeakyClass对象return 0;} // 程序退出时自动调用report()

mem_leak_detector.hpp

#
pragma once
#
ifndef MEM_LEAK_DETECTOR_HPP
#
define MEM_LEAK_DETECTOR_HPP
#
include <map>#include <string>#include <iostream>#include <cstdlib>// 内存分配记录结构体struct MemAllocRecord {void* ptr;// 分配的内存地址size_t size;// 分配的字节数constchar* file;// 分配发生的源文件名int line;// 分配发生的代码行号};class MemLeakDetector {public:// 跟踪内存分配(由重载的new调用)staticvoid track(void* p, size_t size,constchar* file,int line);// 停止跟踪内存(由重载的delete调用)staticvoid untrack(void* p);// 生成泄漏报告(程序退出时调用)staticvoid report();private:static std::map<void*, MemAllocRecord> allocations;// 未释放内存记录表};// 声明带位置信息的全局运算符重载void*operatornew(size_t size,constchar* file,int line);void*operatornew[](size_t size,constchar* file,int line);voidoperatordelete(void* p)noexcept;voidoperatordelete[](void* p)noexcept;// 初始化宏:注册报告函数到atexit#define MEM_LEAK_DETECTOR_INIT() \std::atexit(MemLeakDetector::report)// 在非实现文件中重定义new宏(捕获分配位置)#ifndef MEM_LEAK_DETECTOR_IMPLEMENTATION#define new new(__FILE__, __LINE__) // 替换所有new为带位置信息的版本#endif#endif

mem_leak_detector.cpp

#
define MEM_LEAK_DETECTOR_IMPLEMENTATION // 启用实现模式
#
include "mem_leak_detector.hpp"
#
include <cstring>#include <cstdlib>#include <iostream>// 静态成员初始化:存储所有未释放的内存记录std::map<void*, MemAllocRecord> MemLeakDetector::allocations;// 跟踪内存分配:记录指针、大小、文件名和行号void MemLeakDetector::track(void* p, size_t size,constchar* file,int line) {char* file_copy = strdup(file);// 复制文件名字符串allocations[p] = MemAllocRecord{p, size, file_copy, line};// 存入映射表}// 停止跟踪:内存释放时从映射表移除记录void MemLeakDetector::untrack(void* p) {auto it = allocations.find(p);if (it != allocations.end()) {free((void*)it->second.file);// 释放复制的文件名内存allocations.erase(it);// 移除记录}}// 生成泄漏报告:程序退出时自动调用void MemLeakDetector::report() {if (allocations.empty()) {std::cout <<"\n[SUCCESS] No memory leaks detected\n";}else {std::cout <<"\n[MEMORY LEAKS] " << allocations.size()<<" leaks found:\n";size_t total = 0;// 遍历所有未释放的记录for (constauto& entry : allocations) {const MemAllocRecord& record = entry.second;std::cout <<" Leak at " << record.ptr<<" (" << record.size <<" bytes)"<<" allocated in " << record.file<<":" << record.line <<"\n";total += record.size;// 累计泄漏字节数}std::cout <<"Total leaked: " << total <<" bytes\n";}}// 重载全局new运算符:捕获分配位置信息void*operatornew(size_t size,constchar* file,int line) {void* p = malloc(size);// 实际内存分配if (p) {// 记录分配信息:指针、大小、文件名、行号MemLeakDetector::track(p, size, file, line);}return p;}// 重载全局new[]运算符:转发给带位置信息的newvoid*operatornew[](size_t size,constchar* file,int line) {returnoperatornew(size, file, line);}// 重载全局delete运算符:释放内存并停止跟踪voidoperatordelete(void* p)noexcept {MemLeakDetector::untrack(p);// 从跟踪表中移除free(p);// 实际释放内存}// 重载全局delete[]运算符:转发给deletevoidoperatordelete[](void* p)noexcept {operatordelete(p);}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/930198.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

实用指南:实践篇:利用ragas在自己RAG上实现LLM评估②

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

完整教程:数据结构:递归的种类(Types of Recursion)

完整教程:数据结构:递归的种类(Types of Recursion)pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas&…

专业模板网站制作wordpress apple pro

当代年轻人的生活方式是怎样的&#xff1f;靠地铁通勤&#xff0c;靠咖啡续命早上睁不开眼&#xff0c;咖啡来一杯中午昏昏欲睡&#xff0c;咖啡来一杯晚上熬夜加班&#xff0c;咖啡来一杯喝完这杯&#xff0c;还有一杯“宁可食无肉&#xff0c;不可早无星”是当代年轻人的座右…

网站建设首选wordpress首页不显示指定分类

1、对比 tairzookeper性能高 低可靠性低 高 2、zookeper实现分布式锁 特点&#xff1a; Zookeeper能保证数据的强一致性&#xff0c;用户任何时候都可以相信集群中每个节点的数据都是相同的。 加锁 客户端在ZooKeeper一个特定的节点下创建临时顺序节点&…

Nova Premier模型安全评估结果解析

本文通过第三方评估机构对Nova Premier模型进行黑盒压力测试和红队演练,展示了该模型在安全防护方面的卓越表现,包括在恶意指令抵抗和有害内容生成防护方面的技术细节。独立评估证明 Nova Premier 的安全性 - 某中心…

改写自己的浏览器插件工具 myChromeTools - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

做移动网站优化排名网站建设需要编码不

基本数据类型 Java 基础数据按类型可以分为四大类&#xff1a;布尔型、整数型、浮点型、字符型&#xff0c;这四大类包含 8 种基础数据类型。 布尔型&#xff1a;boolean整数型&#xff1a;byte、short、int、long浮点型&#xff1a;float、double字符型&#xff1a;char 八种…

网站设计需要那些人绍兴seo推广

hash 模式 【推荐】 路由效果 在不刷新页面的前提下&#xff0c;根据 URL 中的 hash 值&#xff0c;渲染对应的页面 http://test.com/#/login 登录页http://test.com/#/index 首页 核心API – window.onhashchange 监听 hash 的变化&#xff0c;触发视图更新 window.onhas…

通过litestream 进行sqlite-vec 数据备份以及恢复

通过litestream 进行sqlite-vec 数据备份以及恢复实际上就是一个简单的测试,litestream 支持流式复制,比较适合对于sqlite进行备份,同时litestream 还支持对象存储的remote 模式,比较方便 环境准备minioservices: …

相册网站开发那个网站开发三味

强制类型转换形式&#xff1a;(类型说明符) (表达式)举例说明&#xff1a;1) int a;a (int)1.9;2)char *b;int *p;p (int *) b; //将b的值强制转换为指向整型数据的指针类型&#xff0c;后赋给p注示&#xff1a;类型说明符和表达式都必须加括号&#xff0c;表达式为单个变量可…

做购物网站适合的服务器网站建设需求模版

目录 前言 一、创建上下文类 1.自定义MyContext上下文类继承IdentityDbContext 2.在Program中添加AddDbContext服务 二、使用Migration数据迁移 1.在控制台中 依次使用add-migration 、updatebase 命令 2.如何修改表名 3.如何自定义字段 三、使用Identity实现登录、修改密码 …

对于路由使用的ref的疑问

<script setup>import { ref, computed } from vueimport Home from ./Home.vueimport About from ./About.vueimport NotFound from ./NotFound.vueconst routes = { /: Home, /about: About}const currentPa…

天津到天津天津网站开发iis v6 新建网站

大数据管理数据处理过程图大数据(big data),指无法在一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合,是需要新处理模式才能具有更强的决策力、洞察力。大数据处理的主要流程包括数据收集、数据存储、数据处理、数据应用等主要环节。随着业务的增长,大量和流程、规…

自建购物网站福建响应式网站制作

目录 详细布置&#xff1a; 1. 层序遍历 2. 226. 翻转二叉树 3. 101. 对称二叉树 详细布置&#xff1a; 1. 层序遍历 昨天练习了几种二叉树的深度优先遍历&#xff0c;包括&#xff1a; ​​​​​​前中后序的递归法前中后序的迭代法前中后序迭代的统一写法 今天&…

扁平化企业网站从零开始制作wordpress主题

文章目录 360篡改浏览器主页方法1锁定浏览器主页 方法2注册表修改 360广告和弹窗360极速版 小结 360篡改浏览器主页 如果您使用360,且不想卸载它,那么当你启动360后,它可能会篡改你的浏览器(比如edge)的主页start page为360早期可能是通过修改快捷方式的target等属性,但是现在…

新网站建设验收公司手机网站模板

&#x1f4cb; 博主简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是wux_labs。&#x1f61c; 热衷于各种主流技术&#xff0c;热爱数据科学、机器学习、云计算、人工智能。 通过了TiDB数据库专员&#xff08;PCTA&#xff09;、TiDB数据库专家&#xff08;PCTP…

公司专业网站建设百度分享代码 wordpress

背景&#xff1a; 想知道四川省包含哪些水系&#xff0c;以及各个水系的分布&#xff0c;起点、流经省市、终点等 {label: "嘉陵江",value: "嘉陵江",},{label: "渠江",value: "渠江",},{label: "涪江",value: "涪江&q…

Paypal 设置不自动换汇

进入 PayPal 网页版自动付款设置。选择结算商户,查看兑换选项,修改为使用银行的兑换方式。

网站建设培训赚钱吗怎么才能在百度上做引流呢

目录 引出Redis持久化方式Redis入门1.Redis是什么&#xff1f;2.Redis里面存Java对象 Redis进阶1.雪崩/ 击穿 / 穿透2.Redis高可用-主从哨兵3.持久化RDB和AOF4.Redis未授权访问漏洞5.Redis里面安装BloomFilte Redis的应用1.验证码2.Redis高并发抢购3.缓存预热用户注册验证码4.R…

威县网站建设报价微信app开发价格表

我们需要将Python对象序列化为字节流&#xff0c;这样就可以将其保存到文件中、存储到数据库中或者通过网络连接进行传输。 解决方案 序列化最普遍的做法是使用 pickle 模块。为了将一个对象保存到一个文件中&#xff0c;可以这样做&#xff1a; import pickledata ... # Some…