C++ 内存泄漏检测器设计

article/2025/7/6 17:09:15

文章目录

  • 1. C++中的动态内存分配
  • 2. 什么是内存泄漏
  • 3. 内存泄漏的代码案例
  • 4. 内存泄漏检查器的设计
    • 模块1:位置信息捕获:
    • 模块2:内存分配跟踪:
    • 模块3:内存释放跟踪:
    • 模块4:泄漏记录存储:
    • 模块5:报告生成:
  • 5. 测试案例


在这里插入图片描述


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

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

先来看段代码:

#include <iostream>
using namespace std;int main() {// 动态分配单个整型变量int* pInt = new int;*pInt = 20;cout << "动态分配的整数数值为: " << *pInt << endl;// 释放内存delete pInt;// 动态分配数组int arraySize;cout << "请指定数组的大小: ";cin >> arraySize;int* dynamicArray = new int[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 = new int;  // 分配内存 (通过重载的new记录位置)
}// 数组内存泄漏示例:分配int数组未释放
void array_leak() {int* arr = new int[10];  // 分配数组 (通过重载的new[]记录)
}// 异常导致的内存泄漏:分配后抛出异常跳过delete
void exception_leak() {int* data = new int; throw std::runtime_error("Oops!");  // 抛出异常delete data;  // 此句不会执行
}// 类内泄漏示例:析构函数未释放成员指针
class LeakyClass {
public:LeakyClass() : buf(new char[1024]) {}  // 分配内存~LeakyClass() {}  // 析构函数未释放buf → 泄漏
private:char* buf;
};// 容器内存泄漏示例:vector存储指针未释放
void container_leak() {std::vector<int*> vec;for (int i = 0; i < 5; ++i) {vec.push_back(new int(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;        // 分配的字节数const char* file;   // 分配发生的源文件名int line;           // 分配发生的代码行号
};class MemLeakDetector {
public:// 跟踪内存分配(由重载的new调用)static void track(void* p, size_t size, const char* file, int line);// 停止跟踪内存(由重载的delete调用)static void untrack(void* p);// 生成泄漏报告(程序退出时调用)static void report();
private:static std::map<void*, MemAllocRecord> allocations;  // 未释放内存记录表
};// 声明带位置信息的全局运算符重载
void* operator new(size_t size, const char* file, int line);
void* operator new[](size_t size, const char* file, int line);
void operator delete(void* p) noexcept;
void operator delete[](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, const char* 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 (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";}
}// 重载全局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[]运算符:转发给带位置信息的new
void* operator new[](size_t size, const char* file, int line) {return operator new(size, file, line);
}// 重载全局delete运算符:释放内存并停止跟踪
void operator delete(void* p) noexcept {MemLeakDetector::untrack(p);  // 从跟踪表中移除free(p);                      // 实际释放内存
}// 重载全局delete[]运算符:转发给delete
void operator delete[](void* p) noexcept {operator delete(p);
}


http://www.hkcw.cn/article/PpMMeSZCqV.shtml

相关文章

线程安全与线程池

概念&#xff1a;多个线程&#xff0c;同时操作同一个共享资源的时候&#xff0c;可能会出现业务安全问题。 出现线程安全问题的条件&#xff0c;原因&#xff1a;1.存在多个线程在同时执行 2.同时访问一个共享资源 3.存在修改该共享资源 线程同步&#xff1a;是线程安全…

网络安全的学习路线是怎么样的?

我是几乎完全自学的&#xff0c;十年前从双非跨专业考研到中科大软件学院网络安全专业&#xff0c;读研之前&#xff0c;C语言是自学的&#xff0c;数据结构是自学的&#xff0c;计算机网络是自学的&#xff0c;操作系统是自学的&#xff0c;微机原理是自学的。为了让我们能跟上…

每日算法-250602

每日算法学习记录 - 250602 今天学习和复习了两道利用前缀和与哈希表解决的子数组问题&#xff0c;特此记录。 560. 和为 K 的子数组 题目 思路 本题的核心思想是利用 前缀和 与 哈希表 来优化查找过程。 解题过程 题目要求统计和为 k 的子数组个数。 我们首先预处理出一…

Hadoop学习笔记

&#xff08;1&#xff09;Hadoop概述 Hadoop是一个开源的分布式计算和存储框架&#xff0c;用于处理大规模数据集&#xff08;大数据&#xff09;的并行处理。它由Apache基金会开发&#xff0c;核心设计灵感来自Google的MapReduce和Google文件系统&#xff08;GFS&#xff09…

PCIe—TS1/TS2 之Polling.Configuration (二)

前文 在 Polling.Configuration 次状态中&#xff0c;发送⽅停⽌发送 TS1 序列&#xff0c;转⽽发送 TS2 序列&#xff0c;TS2 序列中的链路和通道&#xff08;lane&#xff09;字段仍然使⽤填充字段填充。 该状态中&#xff0c;发送⽅转⽽发送 TS2 的⽬的是通知链路对端的设备…

如何增加 cPanel中的 PHP 最大上传大小?

PHP通过限制文件上传大小来保护服务器性能&#xff0c;但默认限制对于许多现代网页应用来说太低了。当PHP应用程序显示错误信息&#xff0c;要求你增加PHP的最大上传文件大小时&#xff0c;你可能会遇到这个问题。有多种方法可以提高上传限制&#xff0c;包括直接编辑PHP配置文…

linux——文件系统

被打开的文件放到内存中没有被打开的文件放到磁盘 1. 硬件-->磁盘 磁盘的存储基本单位&#xff1a;扇区&#xff08;512字节&#xff09; 512字节写入到磁盘&#xff0c;磁盘如何转动&#xff1a; 磁盘写入的时候是向柱面进行批量写入的 CHS地址&#xff1a;cylind heade…

HBM的那些事2 写操作

搞懂写&#xff0c;把下面这幅图搞定&#xff0c;基本上就掌握了7成了。 术语解释 WL &#xff1a; write latency&#xff0c;说的是命令到发送数据的WDQS的间隔&#xff0c;注意这里不包含twpre1的时间&#xff0c;通过配置MR1实现。 twpre1: 在发送写数据之前&#xff0c;W…

B1039 PAT乙级JAVA题解 到底买不买

小红想买些珠子做一串自己喜欢的珠串。卖珠子的摊主有很多串五颜六色的珠串&#xff0c;但是不肯把任何一串拆散了卖。于是小红要你帮忙判断一下&#xff0c;某串珠子里是否包含了全部自己想要的珠子&#xff1f;如果是&#xff0c;那么告诉她有多少多余的珠子&#xff1b;如果…

力扣HOT100之多维动态规划:62. 不同路径

这道题用二维dp数组来做相当简单&#xff0c;是一道入门题。直接上动规五部曲&#xff1a; 1.确定dp[i][j]的含义&#xff1a;从起点到位置为[i][j]处的路径总数 2.确定递推公式 dp[i][j] dp[i - 1][j] dp[i][j - 1]; 3.dp数组初始化 dp[0][j] 1;dp[i][0] 1; 4.确定遍历顺序…

css呼吸灯

效果图 只是简单的呼吸效果&#xff0c;您按照需求自己拓展即可。 代码 keyframes light{from{opacity: 1;}to{opacity: 0.2;}}使用 .view{animation-name: light;animation-duration: 1s;animation-timing-function: linear;animation-iteration-count: infinite;animation-…

AI入门——AI大模型、深度学习、机器学习总结

以下是对AI深度学习、机器学习相关核心技术的总结与拓展&#xff0c;结合技术演进逻辑与前沿趋势&#xff0c;以全新视角呈现关键知识点 一、深度学习&#xff1a;从感知到认知的技术革命 核心突破&#xff1a;自动化特征工程的范式变革 深度学习通过多层神经网络架构&#x…

python训练营打卡第42天

Grad-CAM与Hook函数 知识点回顾 回调函数lambda函数hook函数的模块钩子和张量钩子Grad-CAM的示例 作业&#xff1a;理解下今天的代码即可 1.回调函数 def handle_result(result):"""处理计算结果的回调函数"""print(f"计算结果是: {resul…

ISO18436-2 CATII级振动分析师能力矩阵

ISO18436-2021是当前针对针对分析师的一个标准&#xff0c;它对振动分析师的能力和知识体系做了4级分类&#xff0c;这里给出的是一家公司响应ISO18436的CATII级标准&#xff0c;做的一个专题培训的教学大纲。摘自&#xff1a; 【振動噪音產學技術聯盟】04/19-23 ISO 18436-2…

YARN应用日志查看

YARN应用日志查看 1、页面查看2、命令行查看1、页面查看 1.1、YARN ResourceManager Web UI Spark on YARN时,YARN的资源管理器(ResourceManager)和历史服务器(History Server)提供了强大的日志和监控功能,可以帮助用户查看和管理Spark作业 访问YARN ResourceManager的…

免费酒店管理系统+餐饮系统+小程序点餐——仙盟创梦IDE

酒店系统主屏幕 房间管理 酒店管理系统的房间管理&#xff0c;可实现对酒店所有房间的实时掌控。它能清晰显示房间状态&#xff0c;如已预订、已入住、空闲等&#xff0c;便于高效安排入住与退房&#xff0c;合理分配资源&#xff0c;提升服务效率&#xff0c;保障酒店运营有条…

29 C 语言内存管理与多文件编程详解:栈区、全局静态区、static 与 extern 深度解析

1 C 语言内存管理概述 1.1 内存分区模型解析 在 C 语言程序中&#xff0c;内存的合理管理是确保程序高效运行的核心。为了深入理解变量的作用域、生命周期及内存分配机制&#xff0c;我们需要先掌握内存分区模型。C 语言将内存划分为以下几个核心区域&#xff1a; 栈区&#…

JavaScript 性能优化实战:从原理到框架的全栈优化指南

在 Web 应用复杂度指数级增长的今天&#xff0c;JavaScript 性能优化已成为衡量前端工程质量的核心指标。本文将结合现代浏览器引擎特性与一线大厂实践经验&#xff0c;构建从基础原理到框架定制的完整优化体系&#xff0c;助你打造高性能 Web 应用。 一、性能优化基础&#x…

2025年十大AI幻灯片工具深度评测与推荐

我来告诉你一个好消息。 我们已经亲自测试和对比了市面上最优秀的AI幻灯片工具&#xff0c;让你无需再为选择而烦恼。 得益于AI技术的飞速发展&#xff0c;如今你可以快速制作出美观、专业的幻灯片。 这些智能平台的功能远不止于配色美化——它们能帮你头脑风暴、梳理思路、…

MATLAB 安装与使用详细教程

目录 第一部分&#xff1a;MATLAB 安装教程第二部分&#xff1a;MATLAB 界面介绍第三部分&#xff1a;MATLAB 基础使用第四部分&#xff1a;MATLAB 脚本编程第五部分&#xff1a;MATLAB 编程示例 第一部分&#xff1a;MATLAB 安装教程 1 下载 MATLAB 安装文件 访问 MathWor…