计算机系统应用教程网站

网站首页 > 技术文章 正文

Qt中的内存泄漏 qt内存分析

btikc 2024-10-24 09:32:48 技术文章 8 ℃ 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中的智能指针:

auto_ptr

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

scoped_ptr

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

shared_ptr

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

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对象是在栈上分配的内存空间,删除栈上的内存空间对导致程序奔溃

QT开发交流+赀料君羊:714620761
解决方式:

  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栈上的对象会出错。

解决方式:

调整一下顺序,确保label先于其parent被析构,label析构时将自己从父对象的列表中移除自己,w析构时,children列表中就不会有分配在stack中的对象了

将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也不会有问题。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表