C++内存泄漏:原因、预防、定位

内存泄漏是 C++ 中常见的问题之一,可能导致程序运行时资源消耗过大、性能下降,甚至程序崩溃。

内存泄漏的原因

1. 未释放动态分配的内存

在 C++ 中,通过 new 操作符分配的内存需要手动使用 delete 操作符进行释放。如果忘记或者由于某种原因未释放内存,就会导致内存泄漏。

cppCopy code
int* myArray = new int[10];
// 漏掉 delete 操作

2. 循环引用

在使用智能指针时,如果存在循环引用,指针之间的引用计数可能永远不会降为零,导致内存泄漏。

cppCopy code
class Node {
public:std::shared_ptr<Node> next;
};
std::shared_ptr<Node> node1 = std::make_shared<Node>();
std::shared_ptr<Node> node2 = std::make_shared<Node>();
node1->next = node2;
node2->next = node1;  // 循环引用

3. 异常导致的资源泄漏

在发生异常的情况下,如果没有适当地释放资源,就可能发生内存泄漏。在异常发生时,确保已经分配的资源得到正确释放是很重要的。

cppCopy code
try {int* myData = new int[100];// 可能会发生异常// ...delete[] myData;  // 在发生异常时可能不会执行到这里
} catch (...) {// 处理异常
}

4.虚析构

请问:在STL中std::string等容器能否被继承,为什么?

回答:不能,因为继承需要父类析构函数为virtual,而string类它的析构函数没有提供虚函数。事实上**std::vectorstd::liststd::dequestd::setstd::map等容器都不是设计为可继承的。它们的设计目标是提供高效、通用的数据结构,而不是作为基类供其他类进行继承。


如果继承了string类会怎样?

答案:这样会引起内存泄漏

例如:

class Base {public:Base(){buffer_ = new char[10];}~Base() {std::cout << "in Base::~Base" << std::endl;delete []buffer_;}
private:char *buffer_;
};
class Derived : public Base {public:Derived(){}~Derived() {std::cout << "int Derived::~Derived" << std::endl;}
};
int main() {Base *base = new Derived;delete base;return 0;
}

上面代码输出如下:

in Base::~Base

可见,上述代码并没有调用派生类Derived的析构函数,如果派生类中在堆上申请了资源,那么就会产生内存泄漏

为了避免因为继承导致的内存泄漏,我们需要将父类的析构函数声明为virtual,代码如下(只列了部分修改代码,其他不变):

~Base() {std::cout << "in Base::~Base" << std::endl;delete []buffer_;}

然后重新执行代码,输出结果如下:

int Derived::~Derived
in Base::~Base

总结下存在继承情况下,构造函数和析构函数的调用顺序。

派生类对象在创建时构造函数调用顺序:

  1. 调用父类的构造函数
  2. 调用父类成员变量的构造函数
  3. 调用派生类本身的构造函数

派生类对象在析构时的析构函数调用顺序:

  1. 执行派生类自身的析构函数
  2. 执行派生类成员变量的析构函数
  3. 执行父类的析构函数

为了避免存在继承关系时候的内存泄漏,请遵守一条规则:无论派生类有没有申请堆上的资源,请将父类的析构函数声明为virtual

如何避免内存泄漏

1. 使用智能指针

C++11 引入的智能指针(如 std::shared_ptrstd::unique_ptr)可以在对象生命周期结束时自动释放内存,减少手动内存管理的负担。

std::shared_ptr<int> myInt = std::make_shared<int>(42);

2. RAII(资源获取即初始化)原则

通过使用 RAII,可以确保资源在对象构造时获得,并在对象析构时释放。这种方法减少了手动管理资源的需要,降低了内存泄漏的风险。

class FileHandler {
public:FileHandler(const std::string& fileName) : file(std::fopen(fileName.c_str(), "r")) {if (!file) {throw std::runtime_error("Failed to open file");}}~FileHandler() {if (file) {std::fclose(file);}}// 其他成员函数...
private:FILE* file;
};

3. 使用工具检测内存泄漏

使用工具如 Valgrind、cppcheck等来检测和分析内存泄漏问题。或者一些商用的代码静态分析工具(例如SonarQube)。这些工具能够在运行时提供详细的内存分析报告,帮助找到潜在的问题。

内存泄漏的定位方法

1. 静态分析工具

使用静态分析工具(如cppcheck)可以在编译阶段检测到一些潜在的内存泄漏问题。这些工具通过分析源代码来查找可能导致内存泄漏的模式。

cppcheck --enable=all --inconclusive --std=posix source.cpp

cppcheck还可以搭配jenkins使用,实现自动编译分析,并进行图形化显示。在Jenkins中已经有cppcheck的插件,所以它可以配合jenkins使用。Jenkins可以对cppcheck检测后的结果进行处理,并且可以将结果图形化的显示。

2. 动态分析工具

使用动态分析工具(如 Valgrind)来运行程序,检查其内存使用情况。Valgrind 特别适用于检测未释放的内存。

valgrind --leak-check=full ./your_program

在Linux上比较常用的内存泄漏检测工具是valgrind,所以咱们就以valgrind为工具,进行检测。

我们首先看一段代码:

#include <stdlib.h>void func (void){char *buff = (char*)malloc(10);
}int main (void){func(); // 产生内存泄漏return 0;
}
  • 通过gcc -g leak.c -o leak命令进行编译
  • 执行valgrind --leak-check=full ./leak

在上述的命令执行后,会输出如下:

==9652== Memcheck, a memory error detector
==9652== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==9652== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==9652== Command: ./leak
==9652==
==9652==
==9652== HEAP SUMMARY:
==9652==     in use at exit: 10 bytes in 1 blocks
==9652==   total heap usage: 1 allocs, 0 frees, 10 bytes allocated
==9652==
==9652== 10 bytes in 1 blocks are definitely lost in loss record 1 of 1
==9652==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==9652==    by 0x40052E: func (leak.c:4)
==9652==    by 0x40053D: main (leak.c:8)
==9652==
==9652== LEAK SUMMARY:
==9652==    definitely lost: 10 bytes in 1 blocks
==9652==    indirectly lost: 0 bytes in 0 blocks
==9652==      possibly lost: 0 bytes in 0 blocks
==9652==    still reachable: 0 bytes in 0 blocks
==9652==         suppressed: 0 bytes in 0 blocks
==9652==
==9652== For lists of detected and suppressed errors, rerun with: -s
==9652== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

valgrind的检测信息将内存泄漏分为如下几类:

  • definitely lost:确定产生内存泄漏
  • indirectly lost:间接产生内存泄漏
  • possibly lost:可能存在内存泄漏
  • still reachable:即使在程序结束时候,仍然有指针在指向该块内存,常见于全局变量

主要上面输出的下面几句:

==9652==    by 0x40052E: func (leak.c:4)
==9652==    by 0x40053D: main (leak.c:8)

提示在main函数(leak.c的第8行)fun函数(leak.c的第四行)产生了内存泄漏,通过分析代码,原因定位,问题解决。

valgrind不仅可以检测内存泄漏,还有其他很强大的功能,由于本文以内存泄漏为主,所以其他的功能就不在此赘述了,有兴趣的可以通过valgrind --help来进行查看。

3. 编写自定义的内存分析工具

通过编写自定义的内存分析工具,可以在应用程序中插入代码来跟踪内存分配和释放的情况,从而帮助定位内存泄漏。

#define new DEBUG_NEWvoid* operator new(size_t size, const char* file, int line) {void* p = malloc(size);// 记录分配信息...return p;
}#define DEBUG_NEW new(__FILE__, __LINE__)

在编写代码时,结合使用上述方法,可以显著减少内存泄漏的风险。通过良好的代码设计、使用智能指针和工具的辅助,可以更容易地避免和解决内存泄漏问题。

参考C/C++ 内存泄漏-原因、避免以及定位

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

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

相关文章

调用“每日诗词”在你的页面添加一句诗

概述 前几天浏览网站的时候看到页面上有句诗&#xff0c;打开调试看了下调用的是“每日诗词”的SDK。本文基于此SDK实现你的页面添加一句诗。 实现效果 实现 1. 引入SDK <script src"https://sdk.jinrishici.com/v2/browser/jinrishici.js" charset"utf-…

mysql服务治理

一、性能监控指标和解决方案 1.QPS 一台 MySQL 数据库&#xff0c;大致处理能力的极限是&#xff0c;每秒一万条左右的简单 SQL&#xff0c;这里的“简单 SQL”&#xff0c;指的是类似于主键查询这种不需要遍历很多条记录的 SQL。 根据服务器的配置高低&#xff0c;可能低端…

【BUUCTF web】通关 2.0

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收藏 …

MAC-键盘command快捷键、设置windows快捷键

在 Windows PC 专用键盘上&#xff0c;请用 Alt 键代替 Option 键&#xff0c;用 Ctrl 键或 Windows 标志键代替 Command 键。 Mac 键盘快捷键 - 官方 Apple 支持 (中国) 设置windows快捷键 使用mac外接适用于windows的键盘时&#xff0c;如何设置快捷键&#xff1f;_mac外…

2024年2月国内如何快速注册OnlyFans最新小白教学

前言 onlyface软件是一个创立于2016年的订阅式社交媒体平台&#xff0c;创作者可以在自己的账号发布原创的照片或视频&#xff0c;并将其设置成付费模式&#xff0c;若用户想查看则需要每月交费订阅。 需要注意的是&#xff0c;网络上可能存在非法或不道德的应用程序&#xff…

Java:性能优化细节31-45

Java&#xff1a;性能优化细节31-45 31、合理使用java.util.Vector 在使用java.util.Vector时&#xff0c;需要注意其性能特性和最佳实践&#xff0c;以确保应用程序运行高效。Vector是一个同步的集合类&#xff0c;提供了动态数组的实现。由于它是线程安全的&#xff0c;所以…

获取当前数据 上下移动

点击按钮 上下移动 当前数据 代码 // 出国境管理 登记备案人员列表 <template><a-row><a-col span"24"><a-card :class"style[a-table-wrapper]"><!-- 出国境 登记备案人员列表 --><a-table:rowKey"records >…

淘宝开放平台获取商家订单数据API接口接入流程

taobao.custom 自定义API操作 接口概述&#xff1a;通过此API可以调用淘宝开放平台的API&#xff0c;通过技术对接&#xff0c;您可以轻松实现无账号调用官方接口。进入测试&#xff01; 公共参数 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&…

通过修改host文件来访问GitHub

前言&#xff1a; 由于国内环境的原因&#xff0c;导致我们无法流畅的访问GitHub&#xff0c;。 但是我们可以采取修改host文件来实现流畅访问。 缺点&#xff1a;需要不定时的刷新修改。 操作流程 一、查询IP地址 以下地址可以查询ip地址 http://ip.tool.chinaz.com/ htt…

pugixml使用

pugixml 使用pugixml库需要三个文件:pugiconfig.hpp pugixml.cpp pugixml.hpp pugixml.hpp代码添加在最后。 全是代码 写入文件-使用实例&#xff1a; #include "../pugixml/pugixml.hpp"//2024.2.29 add 写入参数值到文件中 void MainFrame::SaveBrg(CString Path) …

C++从零开始的打怪升级之路(day40)

这是关于一个普通双非本科大一学生的C的学习记录贴 在此前&#xff0c;我学了一点点C语言还有简单的数据结构&#xff0c;如果有小伙伴想和我一起学习的&#xff0c;可以私信我交流分享学习资料 那么开启正题 今天分享的是关于继承的知识点 1.继承的概念及定义 1.1继承的概…

JDK时间

Date 全世界的时间&#xff0c;有一个统一的计算标准。 世界标准时间&#xff1a;格林尼治时间/格林威治时间简称GMT&#xff0c;目前时间标准时间已经替换为&#xff1a;原子钟。 中国标准时间&#xff1a;世界时间8 时间换算单位&#xff1a; 一秒等于一千毫秒 一毫秒等于一…

CDC作业历史记录无法删除问题

背景 数据库开启CDC功能后&#xff0c;每天会生成大量的历史记录&#xff0c;即使达到参数“每个作业的最大历史记录“的阈值后也不会被删除&#xff0c;导致其它作业的历史记录被删除&#xff0c;无法查看以前的执行情况&#xff0c;非常不方便。 现象 数据库开启CDC后会创建…

【MATLAB源码-第147期】基于matlab的QPSK调制解调在AWGN信道,瑞利信道,莱斯信道理论与实际误码率对比仿真。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 四相位移键控&#xff08;QPSK&#xff0c;Quadrature Phase Shift Keying&#xff09;是一种重要的数字调制技术&#xff0c;它通过改变信号的相位来传输数据。与其他调制技术相比&#xff0c;QPSK在相同的带宽条件下能够传…

Linux命名管道

Linux匿名管道-CSDN博客 目录 1.原理 2.接口实现 3.模拟日志 Linux匿名管道-CSDN博客 这上面叫的是匿名管道&#xff0c;不要将两者搞混&#xff0c;匿名管道说的是两个有血缘关系的进程相互通信&#xff0c;但是命名管道就是两个没有关系的管道相互通信。 1.原理 和匿名…

高斯扩散过程

高斯扩散过程是一种数学模型&#xff0c;用于描述某些随机现象的时间演化&#xff0c;其中这些现象的概率密度函数&#xff08;PDF&#xff09;符合高斯分布&#xff0c;也称为正态分布。在物理和工程学领域&#xff0c;此类过程通常被用来描述热扩散、粒子扩散、概率密度演变等…

springboot/ssm公司资产网站Java企业资产统计管理系统web

springboot/ssm公司资产网站Java企业资产统计管理系统web 基于springboot(可改ssm)vue项目 开发语言&#xff1a;Java 框架&#xff1a;springboot/可改ssm vue JDK版本&#xff1a;JDK1.8&#xff08;或11&#xff09; 服务器&#xff1a;tomcat 数据库&#xff1a;mysq…

蓝桥杯刷题--python-16

562. 壁画 - AcWing题库 T=int(input()) j=1 while(j<=T): N = int(input()) a=input() s = [0]*(N+1) # 求前戳和 for i in range(1, N + 1): s[i] = int(a[i-1]) + s[i - 1] # 枚举 # 区间 max_ = float(-inf) k = (N + 2 - …

编译链接实战(25)ThreadSanitizer检测线程安全

ThreadSanitizer&#xff08;又称为TSan&#xff09;是一个用于C/C的数据竞争检测器。在并发系统中&#xff0c;数据竞争是最常见且最难调试的错误类型之一。当两个线程并发访问同一个变量&#xff0c;并且至少有一个访问是写操作时&#xff0c;就会发生数据竞争。C11标准正式将…

【电路笔记】--RC网络-RC微分器

RC微分器 文章目录 RC微分器1、概述2、RC微分器电路3、单脉冲 RC 微分器4、RC 微分器示例5、总结无源 RC 微分器是一个串联 RC 网络,可产生与微分数学过程相对应的输出信号。 1、概述 无源 RC 微分器只不过是与电阻串联的电容,这是一种与频率相关的器件,其电抗与固定电阻串…