对于编程来讲,每种语言都有各自的内存管理机制,他们并不是完全相同。有的语言比如JAVA、H5等无需编程人员关心内存是怎样创建,怎么释放的,系统会对内存自动管理。但是有些语言比如C、C++等,就需要程序员手动进行内存管理,比如需要的时候进行内存申请,不用的时候需要进行手动释放。
在C++中,new与delete必须配对使用(既,有一个new,有且只能有一个delete)。delete少了,可能会造成内存泄漏;delete多了,麻烦更加大了,会造成未知的错误,一般情况下都会造成程序崩溃。所以使用类似C++这种需要手动进行内存管理的语言,一定要特别注意他们各自的内存管理机制。
本次我们讨论一个与上述两个都有区别的内存管理机制-QT的内存半自动管理。
QT的内存管理机制与上面两个都有区别,既不是完全系统管理,也不是完全用户管理,它是半自动管理,也就是特定情况下只需要new,不需要delete。
因为QT中大部分可见控件,都是继承于QObject,而所有继承于QObject的子类,只需要设置了parent(也可以在构造时,使用setParent函数,或者parent的addChild函数),所以,当parent被delete的时候,它的左右子类也会被自动delete,无需手动处理。并且parnet是不区分他的所有child是new出来的,还是在栈上自动分配的,这充分体现delete的强大,可以释放掉任何子对象。所以我们在QT程序中,经常能够看到很多new出来对象,而很少看到对应的delete的原因。
QT怎么能够做到“父销亡,子必毁”呢?我们先来了解一下QObject对象:
解释之前,我们说明一下我们这里面说的parent。我们说的父子关系包含:父对象、子对象、父子关系。这是QT中特有的,与类的继承无关。我们的parent主要是通过参数传递,或函数调用进行设置两个对象的父子关系的,并非继承。
有一下几点需要我们注意一下:
QObject及其派生类的对象,如果parent不为0,既一个对象有parent时,当其parent析构时会析构该对象。
QWidget及其派生类的对象,可以设置Qt::WA_DeleteOnClose标志后,当close时也会析构该对象。
QAbstractAnimation的派生类对象,可以设置QAbstractAnimation::DeleteWhenStoped后,当动画停止,也可以将其析构。
QRunnable::setAutoDelete() 、 MediaSource::setAutoDelete可以达到特定条件能够自动销毁的效果。
接下来,我们说一下C++程序中内存泄漏,对于C++的内存泄漏,总结起来就是一句话:
“new出来的内存没有通过delete合理的释放掉。”
其中有可能是以下几种情况:
- new 出来的对象,没有及时的delete掉。
- new出来一个数组,删除的时候只调用了delete,而非delete[]。导致只有数组第一个对象的析构函数得到执行并回收。其他对象所占用的内存没有回收。
- delete掉一个void*类型的指针,导致没有调用到对象的析构函数,析构的多有清理工作都没有去执行从而导致内存泄漏。
一、对于第一种情况,new出来的对象没有及时delete掉是什么样的?
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QLabel *label = new QLabel("123");
label->show();
}
其中 new出来的label 并没有地方delete掉,所以,这个内存是肯定泄漏了。
但是鉴于QT程序的半自动内存管理机制,我们来讲一下怎麽避免内存泄漏:
我们可以设置label的parent设置成非0,这样我们就可以不用手动delete了。比如:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QLabel *label = new QLabel("123", this);
label->show();
}
这样就会避免内存泄漏的问题,我们只是在new label 的时候,传入了this作为label的parent。所以这时候只要mainwindow析构的时候,会自动将label删除,不会造成内存泄漏。所以也不必手动delete label。这就说明了QT的内存半自动管理机制。
二、第二种情况可能遇到的会比较少。我们先定义一个类:
class Object {
private:
void* data;
const int size;
const char id;
public:
Object(int sz, char c):size(sz), id(c){
data = new char[size];
qDebug() << "Object() " << id << " size = " << size;
}
~Object(){
qDebug() << "~Object() " << id << endl;
delete []data;
}
};
借着,我们在main函数里进行测试:
int main() {
Object* a = new Object(10, 'A');//Object*指针指向一个Object对象;
void* b = new Object(20, 'B');//void*指针指向一个Object对象;
delete a;//执行delete,编译器自动调用析构函数;
delete b;//执行delete,编译器不会调用析构函数,导致data占用内存没有得到回收;
return 0;
}
执行结果如下图:
从执行结果我们可以看到,程序并没有执行b指针(void*)所指对象的析构函数,所以delete一个void*的指针可能会造成内存上的泄漏。
三、第三种情况可能大多数初学者都会遇到:
class MyClass
{
int a;
int b;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyClass* arry1 = new MyClass[100];//创建包含100个MyClass的对象数组arry1并返回数组首地址;
MyClass* arry2 = new MyClass[100];//创建包含100个MyClass的对象数组arry2并返回数组首地址;
delete []arry1;//回收了数组MyClass里的所有对象动态创建时占用的内存空间;
delete arry2;//回收了数组MyClass里的第一个对象动态创建时占用的内存空间,导致其他99个对象的内存空间泄露;
return a.exec();
}
最后总结一句,所有的内存泄漏都是因为new出来的内存没有及时的delete掉。
**
关于使用工具来检测内存是否泄漏看这里:
**https://blog.csdn.net/xiezhongyuan07/article/details/102585577