为啥我的 Qt 程序在断言失败后继续运行?

Posted

技术标签:

【中文标题】为啥我的 Qt 程序在断言失败后继续运行?【英文标题】:Why does my Qt program keeps running after assertion failure?为什么我的 Qt 程序在断言失败后继续运行? 【发布时间】:2020-01-08 17:05:07 【问题描述】:

我正在开发一个 Qt 应用程序,我对 Qt 对断言失败的行为感到非常困惑。确实,我注意到,虽然发生了断言失败,但我的程序仍然运行了一段时间......

考虑以下代码:

class MainWindow : public QMainWindow

    Q_OBJECT

public:
    MainWindow() : crash(false)
    
        QWidget *widget = new QWidget;
        openAct = new QAction(tr("&Open..."), this);
        connect(openAct, &QAction::triggered, this, &MainWindow::open);
        fileMenu = menuBar()->addMenu(tr("&File"));
        fileMenu->addAction(openAct);
    
private slots :
    void        open()
    
        statusBar()->showMessage(tr("Invoked: File|Open"));
        crash = true;
        assert(false);          // 1st assertion failure ; paintEvent() will be called once more anyway
    
private:
    void        paintEvent(QPaintEvent* evt) final
    
        assert(!crash);         // 2nd assertion failure ; execution stops here
        QMainWindow::paintEvent(evt);
    

    QMenu*      fileMenu;
    QAction*    openAct;
    bool        crash;
;

在调试版本中运行此应用程序时,单击File->Open 将调用open(),此时会发生断言失败。我希望程序执行在这里停止。但是,它没有。

确实,下一个绘制事件被正确处理并调用了函数paintEvent()。 (我可以确定调用了paintEvent(),因为函数中的断言失败会弹出第二个断言失败对话框。)

最后,我得到 2 个(模态)调试器对话框,显示 Abort/retry/Ignore。只有第二个对话框可用于交互。并单击此对话框上的 Abort 将调试器切换到与 paintEvent() 中的第二个 assert() 调用对应的代码行。

此时进入VS调试器显示如下调用栈:

... c++ runtime code ...
Example.exe!MainWindow::paintEvent(QPaintEvent * evt) Ligne 37  C++     -> 2nd assertion failure here...
... Qt code ...
[External code] 
Example.exe!MainWindow::paintEvent(QPaintEvent * evt) Ligne 37  C++     -> ...or maybe here
... Qt code ...
[External code] 
>   Menus Example.exe!MainWindow::open() Ligne 31   C++                         -> 1st assertion failure here
... Qt code ...
Example.exe!main(int argc, char * * argv) Ligne 10  C++
Example.exe!WinMain(HINSTANCE__ * __formal, HINSTANCE__ * __formal, char * __formal, int __formal) Ligne 111    C++
[External code] 

我的问题是:

为什么我的应用程序在第一次断言失败时没有中止? 有什么方法可以确保应用程序在第一次断言失败发生时正确中止(与上述情况相同)?

谢谢

【问题讨论】:

这是在 Release 还是 Debug 构建中? @drescherjm 我得到了 2 个调试器对话框:一个用于第一个断言,另一个用于第二个断言(两者都是带有 Abort/Retry/Ignore 按钮的标准调试器对话框) @drescherjm 是的,我的意思是 2 个模态对话框,并且只能与第 2 个交互,其中“Abort”按钮在第 2 个启动调试器assert() 可能与参加 pant 事件有关。 【参考方案1】:

assert(..) 语句只能用于检查调试版本中的条件。它永远不应该用于避免发布版本中的问题,因为断言不会在发布版本中执行。

在 Visual C++ 中,assert(..) 只为 DEBUG 构建生成代码。它在发布版本中什么也不做。来自文档of assert(..) on Microsoft's site:

只有在定义了 _DEBUG 时,断言语句才能编译。否则,编译器将断言视为空语句。因此,断言语句不会在您的最终发布程序中增加开销或性能成本,并允许您避免使用 #ifdef 指令。

Qt 还支持自己的跨平台断言版本:Q_ASSERT(..)Q_ASSERT_X(..)Q_CHECK_PTR(..)。与普通的 assert(..) 一样,这些也仅适用于 Debug 版本。

如果在编译期间定义了 QT_NO_DEBUG,Q_ASSERT()、Q_ASSERT_X() 和 Q_CHECK_PTR() 将展开为空。因此,这些宏的参数不应该有任何副作用。这是 Q_CHECK_PTR() 的错误用法:

就个人而言,如果我在 Qt 中进行开发,我会留在 Qt 中并且不会混用。因此,我更喜欢使用 Q_ASSERT。

【讨论】:

感谢 jwernerny,但我知道 assert() 的用途。在这种情况下,我用它来调试我的应用程序,在这种情况下,它完美地完成了这项工作,向我展示了我的程序中的一个错误。我的问题是我希望我的程序在这种情况下中止。【参考方案2】:

为什么我的应用程序在第一次断言失败时没有中止?

因为你没有要求它。也许您想稍后设置断点并允许程序继续运行。也许你知道这个断言在这种特殊情况下是无害的,并希望忽略它。

在做出中止决定之前,不会尝试中止整个过程。调用assert 的线程只运行assert 代码,直到做出中止的决定。

有什么方法可以确保应用程序在第一次断言失败发生时正确中止(在与上述相同的情况下)?

如果assert 没有完全按照您的意愿执行,请创建您自己的宏来执行您想要执行的任何操作。如果您想立即强制中止该过程,您可以这样做。

【讨论】:

断言失败,所以我希望会弹出一个调试器对话框并询问我是否要中止;这至少应该暂停执行。这不会发生,并且会继续执行,直到发生第二次断言失败,此时会弹出 2 个预期的对话框。 @shrike 它确实会暂停调用线程的执行,但不会暂停整个进程。暂停整个过程并不是真正的事情。 应用程序是单线程的。调用堆栈清楚地表明了这一点:assert() 都发生在同一个调用堆栈中。如果你有安装了Qt sdk的windows机器,请自己试试我的代码,你会看到的。

以上是关于为啥我的 Qt 程序在断言失败后继续运行?的主要内容,如果未能解决你的问题,请参考以下文章

自动化断言失败后,测试用例还会继续执行吗

调试断言失败:_CrtIsValidHeapPointer(pUserData)

使用 ASSERT_DEATH 时,有没有办法在应用程序终止后自动继续执行所有测试?

用QT写程序,为啥运行成功了,却没有程序界面出来啊?

[C#FluentAssertions在断言失败后继续

为啥 Qt 中的 MainWindow 在启动程序后关闭?