当 QApplication 退出时,QT 引发分段错误(核心已转储)

QT raise segmentation fault (core dumped) when QApplication exit

描述

我正在使用 Python & QT 做一些实验。
我使用 Python 通过调用 QT 的函数创建一个 GUI 应用程序。
但是,当我关闭一个简单的 GUI 应用程序时,它引发了 分段错误(核心已转储)

示例代码

C++:

// This is python C++ extension
#ifndef QT_API
#define QT_API

#include "Python.h"
#include <QtWidgets/QApplication>
#include <QtWidgets/QPushButton>

#endif

static QSharedPointer<QApplication> pglobal_app;

// api: init
static PyObject* init_wrapper(PyObject* self, PyObject* args)
{
    int argc = 1;
    char *argv[] = {""};
    pglobal_app = QSharedPointer<QApplication>(new QApplication(argc, argv));
    Py_RETURN_NONE;
}

// api: launch
static PyObject* launch_wrapper(PyObject* self, PyObject* args)
{
    QPushButton button("Exit");
    QObject::connect(&button, &QPushButton::clicked, &QApplication::quit);
    button.show();
    pglobal_app->exec();
    Py_RETURN_NONE;
}

// methods table
static PyMethodDef qt_api_methods[] = {
    { "init", init_wrapper, METH_VARARGS, "init qt_api"},
    { "launch", launch_wrapper, METH_VARARGS, "launch qt_api"},
    { NULL, NULL, 0, NULL }
};

// init qt_api module
PyMODINIT_FUNC initqt_api(void)
{
    (void) Py_InitModule("qt_api", qt_api_methods);
}

Python:

#!/usr/bin/env python
import sys, os
sys.path.append("build/lib.linux-x86_64-2.7")
import qt_api

qt_api.init()
qt_api.launch()
print "BYE"

症状

当我单击 'Exit' 按钮时,程序打印 BYE,然后引发分段错误。

GDB一瞥

我使用 GDB 来检查发生了什么。下面是输出。

#0  0x00007fd9872ed869 in QWindow::destroy() () from /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5
#1  0x00007fd988b8e1ad in QWidgetPrivate::deleteTLSysExtra() () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#2  0x00007fd988b6278d in QWidgetPrivate::deleteExtra() () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#3  0x00007fd988b68cf8 in QWidgetPrivate::~QWidgetPrivate() () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#4  0x00007fd988b68f39 in QWidgetPrivate::~QWidgetPrivate() () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#5  0x00007fd9894be666 in QObject::~QObject() () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#6  0x00007fd988b71c7a in QWidget::~QWidget() () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#7  0x00007fd988ea5e0e in ?? () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#8  0x00007fd988b8d48d in ?? () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#9  0x00007fd9894be666 in QObject::~QObject() () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#10 0x00007fd988b71c7a in QWidget::~QWidget() () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#11 0x00007fd988b8c299 in QDesktopWidget::~QDesktopWidget() () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#12 0x00007fd988b38a5c in QApplication::~QApplication() () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#13 0x00007fd988b38e69 in QApplication::~QApplication() () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#14 0x00007fd9898cf77e in destroy (this=0x233d220)
    at /home/charles/tools/Qt5.4.0/5.4/gcc_64/include/QtCore/qsharedpointer_impl.h:151
#15 deref (d=0x233d220) at /home/charles/tools/Qt5.4.0/5.4/gcc_64/include/QtCore/qsharedpointer_impl.h:469
#16 deref (this=<optimized out>) at /home/charles/tools/Qt5.4.0/5.4/gcc_64/include/QtCore/qsharedpointer_impl.h:464
#17 QSharedPointer<QApplication>::~QSharedPointer (this=<optimized out>, __in_chrg=<optimized out>)
    at /home/charles/tools/Qt5.4.0/5.4/gcc_64/include/QtCore/qsharedpointer_impl.h:305
#18 0x00007fd98ab15259 in __run_exit_handlers (status=0, listp=0x7fd98ae986c8 <__exit_funcs>, 
    run_list_atexit=run_list_atexit@entry=true) at exit.c:82
#19 0x00007fd98ab152a5 in __GI_exit (status=<optimized out>) at exit.c:104

问题

  1. 为什么会发生这种情况以及如何避免这种情况?
  2. 如果我想在另一个线程中从 Python 调用 QApplication::quit(),我应该怎么做?

非常感谢。

让我们看看你的 launch_wrapper:

// api: launch
static PyObject* launch_wrapper(PyObject* self, PyObject* args)
{
    QPushButton button("Exit");
    QObject::connect(&button, &QPushButton::clicked, &QApplication::quit);
    button.show();
    pglobal_app->exec();
    Py_RETURN_NONE;
}

这里创建一个QPushButton,连接它,显示它,然后进入主Qt事件循环。 但是你在堆栈上创建它,所以当你单击那个按钮时,主事件循环 returns,你的 launch_wrapper returns,QPushButton 在退出时被销毁.

现在你的 pglobal_app 是静态的,所以在你的 QPushButton 消失很久之后,所有使用过的代码都返回后它就被销毁了。

但在内部,Qt 为其几乎所有对象保留了父子层次结构,并且父对象拥有子对象。因此,当 QApplication 被销毁时,它也会销毁它的所有子项,包括您的 QPushButton.

所以你有一个对象被销毁了两次。那是双重自由和未定义的行为。因此崩溃。

要解决此问题,请在堆上创建所有 GUI 元素(即 QPushButton* button = new QPushButton("Exit"); 并让 Qt 使用 parent/child 层次结构对其进行清理。