解决因意外库行为导致的段错误和其他问题?

时间:2014-05-12 02:17:36

标签: c++ qt api resources segmentation-fault

我一直在遇到我最近一直在研究的C ++程序的问题。具体来说,我一直在研究一个使用Qt的GUI框架的程序,我一直在遇到似乎与指针的双重删除和异常处理有关的错误。问题是,我觉得我正在使用的API工作方式不完全可预测,因此,我遇到了很多看似违反直觉的错误。我不是世界上经验最丰富的C ++程序员,所以也许有一些总体策略来处理我缺少的新API ..

这是一个例子:我通常总是试图删除我在同一个类中动态分配的对象。换句话说,如果我在类的构造函数或init函数中使用 new 关键字填充指针,那么我通常会确保删除该指针的内容。 class'sdestructor。

以下是给我提出问题的类的类定义的简化示例[ MyProject.h ]:

#ifndef MYPROJECT_H
#define MYPROJECT_H

#include "QObject.h"
class QGuiApplication;
class QQmlApplicationEngine;
#define MYPROJECT MyProject::getInstance()

class MyProject : public QObject
{
    Q_OBJECT
private:
    explicit MyProject(QObject *parent = 0); //singleton..
    MyProject(MyProject const&); //uncopyable..
    void operator=(MyProject const&); //unassignable..
    QGuiApplication * QtGUI;
    QQmlApplicationEngine * QmlAppEngine;

public:
    ~MyProject(void);

    /* Globally available function to get MyProject's singleton instance.
     * You can use the "MYPROJECT" preprocessor macro for shorthand. */
    static MyProject & getInstance(void)
    {
        static MyProject instance;
        return instance;
    }

    int init(int argc, char * argv[]);
    int exec(void);

signals:
public slots:
};

#endif

这就是我简化的 main.cpp

#include "MyProject.h"

int main(int argc, char * argv[])
{
    MYPROJECT.init(argc, argv);
    return MYPROJECT.exec();
}

这是我最初为该课程[ MyProject.cpp ]所拥有的ctor和init():

MyProject::MyProject(QObject *parent) :
    QObject(parent) ,
    QtGUI(NULL) ,
    QmlAppEngine(NULL)
{
}

MyProject::~MyProject(void)
{
    //segfault: debug points to both of these..
    if (QtGUI) delete QtGUI;
    if (QmlAppEngine) delete QmlAppEngine;
}

int MyProject::init(int argc, char * argv[])
{
    QtGUI = new QGuiApplication(argc, argv);
    QmlAppEngine = new QQmlApplicationEngine();

    if(QtGUI && QmlAppEngine)
    {
        //segfault: debug points to this..
        QmlAppEngine->load(QUrl( QStringLiteral("qrc:///MyProject.qml") ));
    }
    else return 1;
}

int MyProject::exec(void)
{
    return QtGUI->exec();
}

所以,我的计划是:ctor将指针初始化为NULL,init()使用新对象填充这些指针,如果这些指针不为null,则dtor会使用delete清除它们。但是这个代码崩溃了2个段错误,但即使我认为我已经缩小了它,我也不确定我理解为什么它们都发生了。

(1)Segfault#1是启动时崩溃,指向init()内部的“QmlAppEngine-> load()”调用。我能够通过在异常处理代码中包装该函数调用来防止崩溃:

int MyProject::init(int argc, char * argv[])
{
    QtGUI = new QGuiApplication(argc, argv);
    QmlAppEngine = new QQmlApplicationEngine();

    if(QtGUI && QmlAppEngine)
    {
        //exception handling prevents crash..
        try
        {
            QmlAppEngine->load(QUrl( QStringLiteral("qrc:///MyProject.qml") ));
        }
        catch(int e)
        {
            std::cout << "Exception: " << e << std::endl;
            return 1;
        }
        return 0;
    }
    else return 1;
}

我对异常处理不是很熟悉,因为到目前为止我编写的大部分代码都使用了int返回代码样式的错误处理。我猜测加载函数可以在某些情况下抛出异常,而不处理它们会导致崩溃。一旦我做了这个改变,程序在启动时就停止了崩溃,但奇怪的是,它实际上似乎没有抛出异常,因为我的'cout'从不输出任何东西。其他我不明白的是这个代码是在Qt Creator的全新Qt项目的默认设置中调用时没有任何异常处理代码 - 例如,这是您在QtCreator IDE中启动新QtQuick2项目时所看到的:

#include "QGuiApplication"
#include "QQmlApplicationEngine"

int main(int argc, char * argv[])
{
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine();

    //default Qt file calls this without issue though..
    engine.load(QUrl( QStringLiteral("qrc:///MyQml.qml") );

    return app.exec();
}

我在这里看到的唯一主要不同之处是默认代码使用对象而不是指向对象的指针。但是,在这种情况下,加载运行正常,没有异常处理代码,并且没有段错误。

(2)当我的dtor在这两个指针上调用delete关键字时,会引起下一个问题。如果我注释掉这两行,程序运行正常,关闭时不会出现崩溃或问题。这让我相信API已经使这些对象稍后自行删除,当我也显式调用delete时,由于双删除而导致段错误。但是,一般来说,如何知道他们正在使用的API是否在内部处理对象删除?并且,如果我无法判断API指定对象是否被自动删除,我是否应采取任何额外措施(即:使用某种智能指针等)?通常我假设我应该删除在同一个类的析构函数中的任何动态分配的对象,但显然在这种情况下会适得其反。

那么我可以采取哪些步骤来处理我使用的API,其方式是:(a)防止错误;(b)允许我确保正确释放资源并处理异常?

1 个答案:

答案 0 :(得分:1)

通过查看您提供的示例代码很难找到错误的确切位置,您的应用程序必须具有大型代码库并且可以使用内存执行许多操作。 Qt是一个设计良好且文档齐全的框架(虽然有些文档有误导性或过时),如果您有困惑,我建议您正确阅读有关特定项目的文档。以下是我在使用Qt时应该知道/考虑的一些常见问题:

  1. 在继承QObject的类堆上创建对象时,如果在构造函数中传递父(另一个QObject),则子对象拥有父对象将自动释放父级和内存。
  2. QObjectNO_COPYABLE,因此如果您继承,则无需将copy ctor/assignment operator设为私有。编译器生成的这些方法的版本调用父版本(此处为QObject),因此您的类是自动不可复制/可分配的。
  3. 默认情况下,
  4. new如果失败而不是返回bad_alloc则会引发NULL异常。因此,无论您使用try-catch还是使用no_throw new版本new(std::nothrow)更改默认行为,它都会在失败时返回nullptr
  5. delete NULL指针不会导致任何问题。但是,如果pointer指向任意位置/包含垃圾值,delete将导致segfault
  6. 默认情况下engine.load不与异常处理程序一起使用,因此很有可能不会引发异常。仔细查看代码的其他区域。