Qt的对象树模型 Qt内存泄漏的原因分析

0 417
索鸟 2020-12-09
需要:0索币

内存泄漏是指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

内存泄漏非常常见,解决方案有两种:一种是事前预防,比如使用智能指针;另一种是事后查错,比如使用内存泄漏检测工具。

Qt有一套属于自己的内存管理机制,我们想要针对Qt中的内存泄漏,就先要了解Qt中是如何进行内存管理的。

Qt的对象树模型

QT对象之间可以存在父子关系,每一个对象都可以保存它所有子对象的指针,每一个对象都有一个指向其父对象的指针。当指定QT对象的父对象时,父对象会在子对象链表中加入该对象的指针,该对象会保存指向其父对象的指针。当QT对象被销毁时,将自己从父对象的子对象链表中删除,将自己的子对象链表中的所有对象销毁。QT对象销毁时解除和父对象之间的父子关系,并销毁所有的子对象。

C++中 delete 和 new 必须配对使用,如果delete少了,则内存泄露,多了麻烦更大。而Qt中使用了new却很少delete,就是基于对象树模型。当parent被delete时,这个parent的相关所有child都会自动delete,不用用户手动处理。

但是这里也有问题需要我们去考虑,parent是不会区分它的child是new出来的还是在栈上分配的。这体现delete的强大,可以释放掉任何的对象,而delete栈上对象就会导致内存出错,这就需要我们去了解Qt的半自动内存管理。
还有另一个问题是child不知道它自己是否被delete掉了,所以可能会出现野指针。这就需要我们去了解Qt的智能指针。

Qt的半自动化内存管理

  • QObject及其派生类的对象,如果其parent不是nullptr,那么其parent析构时会析构该对象。
  • Widget及其派生类的对象,需要设置 Qt::WA_DeleteOnClose 标志位,才会在close的时候析构对象。
  • AbstractAnimation派生类的对象,可以设置 QAbstractAnimation::DeleteWhenStopped。
  • Runnable::setAutoDelete()、MediaSource::setAutoDelete()。

智能指针

如果没有智能指针,程序员必须保证new对象能在正确的时机delete,四处编写异常捕获代码以释放资源,而智能指针则可以在退出作用域时(不管是正常流程离开或是因异常离开)总调用delete来析构在堆上动态分配的对象

这里介绍几个C++和Qt中的智能指针:

  1. auto_ptr
    auto_ptr 是C++98标准化引入的,带有缺陷的设计,虽说简单,但使用起来却到处是坑,以至于大家都不提倡使用。

  2. scoped_ptr
    scoped_ptr 是C++11标准化引入的,可以防止拷贝,设计简单粗暴但是功能不全,用的也不是很多。

  3. shared_ptr
    shared_ptr 是C++11标准化引入的,引用计数的智能指针,被奉为裸指针的完美替身,因此被广泛使用。功能强大支持拷贝、支持定制删除器,但也存在缺陷,那就是可以循环引用,需要和 weak_ptr 配合来解决。

  4. QPointer
    QPointer是一个模板类。它很类似一个普通的指针,不同之处在于,QPointer 可以监视动态分配空间的对象,并且在对象被 delete 的时候及时更新。
    QPointer的现实原理:在QPointer中保存了一个QObject的指针,并把这个指针的指针(双指针)交给全局变量管理,而QObject 在销毁时会调用 QObjectPrivate::clearGuards 函数来把全局 GuardHash 的那个双指针置为*零,因为是双指针的问题,所以QPointer中指针当然也为零了。用isNull 判断就为空了。

垃圾回收机制 QObjectCleanupHandler

Qt 对象清理器 QObjectCleanupHandler 是实现自动垃圾回收的很重要的一部分。QObjectCleanupHandler 可以监视多个QObject对象的生命周期。并且最大的优点是,当对象在别的地方被删除后,会自动从 QObjectCleanupHandler 中移除,并且可以通过isEmpty()来判断当前 QObjectCleanupHandler 中是否还有监视对象。然后可以使用 clear() 方法直接删除所有的监视对象,而且当 QObjectCleanupHandler 对象析构后,也会自动删除所有监视对象。

内存泄漏示例

示例1:

#include <QApplication>  
#include <QLabel>  

int main(int argc, char *argv[])  
{  
    QApplication a(argc, argv);  
    QLabel *label = new QLabel("Hello World !");  
    label->show();  
    return a.exec();  
}  

解析:这里的 label 没有指定parent,也没有调用delete,所以会造成内存泄漏
解决方式:

  1. 在栈上分配对象,而不是堆上
  2. 设置标志位 Qt::WA_DeleteOnClose,close() 后会 delete
  3. 手动 delete label
示例2:

#include <QApplication>  
#include <QLabel>  

int main(int argc, char *argv[])  
{  
    QApplication app(argc, argv);  
    QLabel label("Hello World !");  
    label.show();  
    label.setAttribute(Qt::WA_DeleteOnClose);  
    return app.exec();  
}

解析:设置标志位 Qt::WA_DeleteOnClose,close() 后会 delete。但label对象是在栈上分配的内存空间,删除栈上的内存空间对导致程序奔溃
解决方式:

  1. 在堆上分配对象,而不是栈上
  2. 不设置标志位 Qt::WA_DeleteOnClose
示例3:

#include <QApplication>  
#include <QLabel>  

int main(int argc, char* argv[])  
{  
   QApplication app(argc, argv);  
   QLabel label("Hello World !");  
   QWidget w;  
   label.setParent(&w);  
   w.show();  
   return app.exec();  
}  

解析:w比label先被析构,当w被析构时,会删除chilren列表中的对象label,但label是分配到栈上的,delete栈上的对象会出错
解决方式:

  1. 调整一下顺序,确保label先于其parent被析构,label析构时将自己从父对象的列表中移除自己,w析构时,children列表中就不会有分配在stack中的对象了
  2. 将label在堆上分配对象,而不是栈上
示例4:

#include <QApplication>  
#include <QLabel>  

int main(int argc, char* argv[])  
{  
   QApplication app(argc, argv);  
   QWidget *w = new QWidget;  
   QLabel *label = new QLabel("Hello World !");  
   label->setParent(w);  
   w->show();  
   delete w;  
   label->setText("go");     //野指针  
   return app.exec();  
}  

解析:程序异常结束,delete w时会delete label,label成为野指针,调用label->setText(“go”) 时出错
解决方式:使用智能指针代替

示例5:

void QObject::deleteLater()  
{  
    QCoreApplication::postEvent(this, new QEvent(QEvent::DeferredDelete));  
} 

解析:当一个QObject正在接受事件队列时被销毁掉会出错
解决方法:QT中建议不要直接Delete掉一个QObject,如果一定要这样做,要使用QObject的deleteLater()函数,它会让所有事件都发送完一切处理好后马上清除这片内存,而且就算调用多次的deletelater也不会有问题。

检测工具

  1. Linux 系统下的内存泄露检查工具
  2. Windows 系统下的内存泄露检查工具

  1. 内存泄露检测工具比较

  1. 原文地址:

  1. https://blog.csdn.net/qq_34139994/article/details/105300959

回帖
  • 消灭零回复
相关主题
2020年最新最新Kubernetes视频教程(K8s)教程 2
程序员转型之制作网课变现,月入过万告别996 1
索鸟快传2.0发布啦 1
两个不同网络的电脑怎么实现文件的互相访问呢? 1
网盘多账号登录软件 1
Java实战闲云旅游项目基于vue+element-ui 1
单点登录技术解决方案基于OAuth2.0的网关鉴权RSA算法生成令牌 1
QT5获取剪贴板上文本信息QT设置剪贴板内容 1
springboot2实战在线购物系统电商系统 1
python web实战之爱家租房项目 1
windows COM实用入门教程 1
C++游戏开发之C++实现的水果忍者游戏 1
计算机视觉库opencv教程 1
node.js实战图书管理系统express框架实现 1
C++实战教程之远程桌面远程控制实战 1
相关主题
PHP7报A non well formed numeric value encountered 0
Linux系统下关闭mongodb的几种命令分享 0
mongodb删除数据、删除集合、删除数据库的命令 0
Git&Github极速入门与攻坚实战课程 0
python爬虫教程使用Django和scrapy实现 0
libnetsnmpmibs.so.31: cannot open shared object file 0
数据结构和算法视频教程 0
redis的hash结构怎么删除数据呢? 0
C++和LUA解析器的数据交互实战视频 0
mongodb errmsg" : "too many users are authenticated 0
C++基础入门视频教程 0
用30个小时精通C++视频教程可能吗? 0
C++分布式多线程游戏服务器开发视频教程socket tcp boost库 0
C++培训教程就业班教程 0
layui的util工具格式时间戳为字符串 0
C++实战教程之远程桌面远程控制实战 1
网络安全培训视频教程 0
LINUX_C++软件工程师视频教程高级项目实战 0
C++高级数据结构与算法视频教程 0
跨域问题很头疼?通过配置nginx轻松解决ajax跨域问题 0