Qt5 连接导致 Unhandled Exception 或 HEAP CORRUPTION

Posted

技术标签:

【中文标题】Qt5 连接导致 Unhandled Exception 或 HEAP CORRUPTION【英文标题】:Qt5 connection causes Unhandled Exception or HEAP CORRUPTION 【发布时间】:2015-08-10 11:19:03 【问题描述】:

我在 VS2010SP1 上使用 Qt5.5。我在停靠小部件中有一个 QCheckBox,它控制同一停靠小部件中的 QTableView(提示为 ConsoleWindowView)是否自动滚动。

问题一:

当我将 QCheckBox::stateChanged 信号连接到 QTableView 的插槽 (autoScroll) 时,一切似乎都很好,除非我更改 QCheckBox 的状态并退出。退出时出现错误:

调试错误!程序:QtTests.exe 检测到堆损坏:之后 0x02B63850 处的普通块 (#4613)。 CRT 检测到应用程序 堆缓冲区结束后写入内存。

如果我没有激活QCheckBox就退出,退出是正常的。

问题 2:

认为这可能与销毁顺序有关,我尝试在 QTableView 析构函数中“记住”连接和断开连接,但是当我尝试将 QObject::connect 的返回值分配给 QMetaObject::Connection 成员变量时,我得到了:

QtTests.exe 中 0x669859c2 处未处理的异常:0xC0000005:访问 违规写入位置0xabababc7

代码如下,后面是相关的ui文件。

崩溃的区域在 Gui.h 中,方法 connectControls。对于问题1,配置为:

QObject::connect(AutoScrollCheckBox, &QCheckBox::stateChanged, this, &ConsoleWindowView::autoScroll); // CRASH1: Debug Error! Program: QtTests.exe HEAP CORRUPTION DETECTED: after Normal block (#4613) at 0x02B63850. CRT detected that the application wrote to memory after end of heap buffer.
//QMetaObject::Connection NewConnection = QObject::connect(AutoScrollCheckBox, &QCheckBox::stateChanged, this, &ConsoleWindowView::autoScroll);
//AutoScrollConnection = NewConnection; // CRASH2: Unhandled exception at 0x669859c2 in QtTests.exe: 0xC0000005: Access violation writing location 0xabababc7

对于问题2,配置为:

//QObject::connect(AutoScrollCheckBox, &QCheckBox::stateChanged, this, &ConsoleWindowView::autoScroll); // CRASH1: Debug Error! Program: QtTests.exe HEAP CORRUPTION DETECTED: after Normal block (#4613) at 0x02B63850. CRT detected that the application wrote to memory after end of heap buffer.
QMetaObject::Connection NewConnection = QObject::connect(AutoScrollCheckBox, &QCheckBox::stateChanged, this, &ConsoleWindowView::autoScroll);
AutoScrollConnection = NewConnection; // CRASH2: Unhandled exception at 0x669859c2 in QtTests.exe: 0xC0000005: Access violation writing location 0xabababc7

ma​​in.cpp

#include <QtWidgets/QApplication>

#include "Gui.h"

int main(int argc, char *argv[])

    QApplication a(argc, argv);
    Gui w;
    w.show();
    return a.exec();

Gui.h

#ifndef QTTESTS_H
#define QTTESTS_H

#include <iostream>

#include <QTimer>
#include <QHeaderView>
#include <QCheckBox>
#include <QObject>

#include "ui_Gui.h"

class ConsoleWindowModelClass : public QAbstractTableModel

    Q_OBJECT
public:
    ConsoleWindowModelClass(QObject *parent)
        : QAbstractTableModel(parent)
        , RowCount(0)
    
        //
        ControllerTimer = new QTimer(this);
        connect(ControllerTimer, SIGNAL(timeout()), this, SLOT(updateController()));
        ControllerTimer->start(2000);
    
    int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE
    
        return RowCount;
    
    int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE
    
        return 2;
    
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE
    
        if (role == Qt::DisplayRole)
        
            return QString("Row%1, Column%2")
                .arg(index.row() + 1)
                .arg(index.column() +1);
        
        return QVariant();
    
    QVariant headerData(int section, Qt::Orientation orientation, int role) const Q_DECL_OVERRIDE
    
        if (orientation == Qt::Horizontal)
        
            if (role == Qt::DisplayRole)
            
                switch(section)
                
                case 0:
                    return "Stamp";
                    break;
                case 1:
                    return "Text";
                    break;
                default:
                    return QString("Column %1").arg(section + 1);
                    break;
                
            
        
        else if (orientation == Qt::Vertical)
        
            if (role == Qt::DisplayRole)
            
                return QString("%1").arg(section + 1);
            
        
        return QVariant();
    
protected:
    unsigned int RowCount;
    QTimer *ControllerTimer;
    private slots:
        // GUI triggered
        // Timer triggered
        void updateController()
        
            beginInsertRows(QModelIndex(), RowCount, RowCount);
            RowCount++;
            endInsertRows();
        
;

class ConsoleWindowView : public QTableView

    Q_OBJECT

public:
    ConsoleWindowView(QWidget *parent = 0)
        : QTableView(parent)
        , AutoScroll(true)
    
    
    virtual ~ConsoleWindowView()
    
        //disconnect(AutoScrollConnection);
    
    void setTheModel(QAbstractItemModel *TheModel)
    
        QTableView::setModel(TheModel);
        connect(model(), &QAbstractItemModel::rowsInserted, this, &ConsoleWindowView::modelRowsInserted);
    
    void connectControls(QCheckBox *AutoScrollCheckBox, QCheckBox *ReverseList, QPushButton *MarkerA, QPushButton *MarkerB, QPushButton *MarkerC, QPushButton *MarkerD)
    
        try
        
            //QObject::connect(AutoScrollCheckBox, &QCheckBox::stateChanged, this, &ConsoleWindowView::autoScroll); // CRASH1: Debug Error! Program: QtTests.exe HEAP CORRUPTION DETECTED: after Normal block (#4613) at 0x02B63850. CRT detected that the application wrote to memory after end of heap buffer.
            QMetaObject::Connection NewConnection = QObject::connect(AutoScrollCheckBox, &QCheckBox::stateChanged, this, &ConsoleWindowView::autoScroll);
            AutoScrollConnection = NewConnection; // CRASH2: Unhandled exception at 0x669859c2 in QtTests.exe: 0xC0000005: Access violation writing location 0xabababc7
        
        catch (const std::exception &Exception)
        
            std::cerr << "ConsoleWindowView::connectControls: " << Exception.what() << std::endl;
        
    

    public slots:
        void modelRowsInserted(const QModelIndex & parent, int start, int end)
        
            if (model() != nullptr)
            
                if (AutoScroll)
                
                    scrollTo(model()->index(start, 0));
                
            
        
        void autoScroll(int State)
        
            if (State == 0)
            
                AutoScroll = false;
            
            else
            
                AutoScroll = true;
            
        

signals:

protected:
    bool AutoScroll;
    QMetaObject::Connection AutoScrollConnection;
private:
;

class Gui : public QMainWindow

    Q_OBJECT

public:
    Gui(QWidget *parent = 0);
    ~Gui();

protected:
    ConsoleWindowModelClass *ConsoleWindowModel;

private:
    Ui::Gui ui;
    void createConsoleWindow();

    //
private slots:
    // GUI triggered

;

#endif // QTTESTS_H

Gui.cpp

#include <iostream>

#include <QTimer>
#include <QHeaderView>
#include <QCheckBox>
#include <QObject>

// Qt model handling the BusSimLowRateTx message from BusSim to DataSwitch.

#include <QAbstractTableModel>

#include "Gui.h"

Gui::Gui(QWidget *parent)
    : QMainWindow(parent)

    // create UI
    ui.setupUi(this);
    //
    ui.actionExit->setShortcuts(QKeySequence::Quit);
    ui.actionExit->setStatusTip(tr("Exit the application"));
    connect(ui.actionExit, SIGNAL(triggered()), this, SLOT(close()));
    // create models
    ConsoleWindowModel = new ConsoleWindowModelClass(this); // should be deleted as part of QMainWindow destructor, by specifying this as the parent.
    //
    // connect ConsoleWindowModel to the View(s)
    createConsoleWindow();


Gui::~Gui()

    // ConsoleWindowModel - should be deleted as part of QMainWindow destructor.
    //ConsoleWindowView *View = static_cast<ConsoleWindowView *>(ui.ConsoleOutputTable);
    //disconnect(ui.AutoScroll, &QCheckBox::stateChanged, View, &ConsoleWindowView::autoScroll);


void Gui::createConsoleWindow()

    ConsoleWindowView *View = static_cast<ConsoleWindowView *>(ui.ConsoleOutputTable);
    View->setTheModel(ConsoleWindowModel);
    for (int c = 1; c < View->horizontalHeader()->count(); ++c)
    
        View->horizontalHeader()->setSectionResizeMode(c, QHeaderView::Stretch);
    
    // connect the other ConsoleWindow Controls to the model
    View->connectControls(ui.AutoScroll, ui.NewestAtTop, ui.MarkerA, ui.MarkerB, ui.MarkerC, ui.MarkerD);

Gui.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Gui</class>
 <widget class="QMainWindow" name="Gui">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>593</width>
    <height>652</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Gui Tests</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <property name="sizePolicy">
    <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
     <horstretch>0</horstretch>
     <verstretch>0</verstretch>
    </sizepolicy>
   </property>
   <layout class="QGridLayout" name="gridLayout_7"/>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>593</width>
     <height>21</height>
    </rect>
   </property>
   <widget class="QMenu" name="menuFile">
    <property name="title">
     <string>File</string>
    </property>
    <addaction name="actionExit"/>
   </widget>
   <widget class="QMenu" name="menuView">
    <property name="title">
     <string>View</string>
    </property>
   </widget>
   <addaction name="menuFile"/>
   <addaction name="menuView"/>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
  <widget class="QDockWidget" name="ConsoleOutput">
   <property name="windowTitle">
    <string>Console Output</string>
   </property>
   <attribute name="dockWidgetArea">
    <number>8</number>
   </attribute>
   <widget class="QWidget" name="ConsoleOutputDock">
    <layout class="QGridLayout" name="gridLayout_4">
     <item row="0" column="0">
      <layout class="QGridLayout" name="gridLayout">
       <item row="0" column="0">
        <layout class="QHBoxLayout" name="horizontalLayout">
         <item>
          <widget class="QCheckBox" name="AutoScroll">
           <property name="text">
            <string>Auto Scroll</string>
           </property>
           <property name="checked">
            <bool>true</bool>
           </property>
          </widget>
         </item>
         <item>
          <widget class="QCheckBox" name="NewestAtTop">
           <property name="text">
            <string>Newest At Top</string>
           </property>
          </widget>
         </item>
         <item>
          <widget class="QPushButton" name="MarkerA">
           <property name="styleSheet">
            <string notr="true">background-color: rgb(255, 0, 0);</string>
           </property>
           <property name="text">
            <string>Marker A</string>
           </property>
          </widget>
         </item>
         <item>
          <widget class="QPushButton" name="MarkerB">
           <property name="styleSheet">
            <string notr="true">background-color: rgb(0, 255, 0);</string>
           </property>
           <property name="text">
            <string>Marker B</string>
           </property>
          </widget>
         </item>
         <item>
          <widget class="QPushButton" name="MarkerC">
           <property name="styleSheet">
            <string notr="true">background-color: rgb(255, 255, 0);</string>
           </property>
           <property name="text">
            <string>Marker C</string>
           </property>
          </widget>
         </item>
         <item>
          <widget class="QPushButton" name="MarkerD">
           <property name="styleSheet">
            <string notr="true">background-color: rgb(0, 0, 255);
color: rgb(255, 255, 255);</string>
           </property>
           <property name="text">
            <string>Marker D</string>
           </property>
          </widget>
         </item>
        </layout>
       </item>
       <item row="1" column="0">
        <widget class="QTableView" name="ConsoleOutputTable"/>
       </item>
      </layout>
     </item>
    </layout>
   </widget>
  </widget>
  <action name="actionExit">
   <property name="text">
    <string>Exit</string>
   </property>
  </action>
  <action name="actionECIO_Outputs">
   <property name="text">
    <string>Enlightenment Monitor</string>
   </property>
  </action>
  <action name="actionECIO_Inputs">
   <property name="text">
    <string>Enlightenment Control</string>
   </property>
  </action>
  <action name="actionSHM_Control">
   <property name="text">
    <string>SHM Input</string>
   </property>
  </action>
  <action name="actionSHM_Output">
   <property name="text">
    <string>SHM Output</string>
   </property>
  </action>
  <zorder>ConsoleOutput</zorder>
 </widget>
 <resources/>
 <connections/>
</ui>

【问题讨论】:

QObject 是一个设计合理的 C++ 类,它始终可以安全地破坏。 在任何情况下您都不需要仅仅因为您碰巧破坏了所涉及的对象而手动断开连接。您的代码还有其他问题,但添加这样的“变通办法”并没有任何帮助。 我怀疑以前从未有过这种情况。我认为控件和 QTableView 之间的关系是错误的,但我看不到如何修复它。 【参考方案1】:

您将QTableView 的实例静态转换为ConsoleWindowView。它不会起作用,因为它不是ConsoleWindowView。你期待什么?你为什么static_cast它?

如果您不确定静态转换是否有效,请使用qobject_cast。它会失败,产生一个 nullptr 结果,你会知道你必须停下来弄清楚为什么你没有得到正确的类实例。你不是因为你的 UI 文件损坏了 - 你必须在 UI 文件中有正确类型的实例。

修复您的 UI 文件以实例化正确的类:

    在设计视图中右键单击 ConsoleOutputTable。 点击推广到...。 在提升的类名中输入ConsoleWindowView。 在头文件中输入gui.h。 点击添加。 点击推广

在任何情况下,您都不需要手动跟踪对象连接。

其他点/咆哮:

    如果你想要一个计时器实例,只需在其中放置一个QTimer 成员 - 使用指针并通过间接引用和额外的堆分配过早悲观有什么意义?让您的生活更轻松。

    抽象项目视图已经有一个名为autoScroll 的属性。你把一个新财产的同名弄乱了,让每个人的生活都变得糟糕透顶。将其命名为其他名称,例如 scrollToNewest

    您的自动滚动表视图应该具有控制其行为的属性。然后,您可以将该属性连接到一个有助于了解源控件(如复选框)的位置的控件。将视图紧密绑定到一些随机控件是一个坏主意。观点应该是一般的。您使用它的表单了解其他控件,并且知道如何将视图绑定到这些控件。

    setModel 是一个虚方法。覆盖它,不要创建自己的。

    变量名以大写字母开头会让你很困惑。不要那样做。类以大写字母开头,变量和成员以小写字母开头。

    signalsslots 部分宏的使用仅应在信号和/或插槽在逻辑上相关并且其中有很多时使用 - 如此之多,以至于使用 Q_SIGNAL 或 @ 987654335@ 前缀会有点多。在大多数情况下,您有少量信号/插槽,您应该使用前缀。然后,您可以轻松地将信号和槽声明与其他相关方法分组,以便声明分组遵循功能,而不是实现细节。

    在 Qt 5 中,您根本不需要 Q_SLOT 前缀,任何方法都可以使用新的 connectsyntax 作为插槽。如果您希望方法的元数据在运行时可用,则只需要 Q_SLOTQ_INVOKABLE 前缀。

考虑到上述情况,ConsoleWindowView 类应该是这样的:

class ConsoleWindowView : public QTableView

   Q_OBJECT
   Q_PROPERTY(bool scrollToNewest READ scrollToNewest WRITE setScrollToNewest)
   bool m_scrollToNewest;
   void modelRowsInserted(const QModelIndex &, int start, int end) 
      Q_UNUSED(end)
      if (model() && m_scrollToNewest) scrollTo(model()->index(start, 0));
   
public:
   ConsoleWindowView(QWidget *parent = 0) : QTableView(parent), m_scrollToNewest(true)
   
   void setModel(QAbstractItemModel *TheModel) Q_DECL_OVERRIDE
   
      QTableView::setModel(TheModel);
      connect(model(), &QAbstractItemModel::rowsInserted, this, &ConsoleWindowView::modelRowsInserted);
   
   Q_SLOT void setScrollToNewest(bool s)  m_scrollToNewest = s; 
   bool scrollToNewest() const  return m_scrollToNewest; 
;

Gui 类,在将控制台视图与用于调整它的具体控件分离后:

class Gui : public QMainWindow

   Q_OBJECT
   Ui::Gui ui;
   ConsoleWindowModel consoleWindowModel;
   void connectConsoleWindow() 
      auto view = ui.ConsoleOutputTable;
      view->setModel(&consoleWindowModel);
      for (int c = 1; c < view->horizontalHeader()->count(); ++c)
         view->horizontalHeader()->setSectionResizeMode(c, QHeaderView::Stretch);

      connect(ui.AutoScroll, &QCheckBox::toggled, view, &ConsoleWindowView::setScrollToNewest);
   

public:
   Gui(QWidget *parent = 0) : QMainWindow(parent) 
      ui.setupUi(this);
      ui.actionExit->setShortcuts(QKeySequence::Quit);
      ui.actionExit->setStatusTip(tr("Exit the application"));
      connect(ui.actionExit, &QAction::triggered, this, &QWidget::close);
      connectConsoleWindow();
   
;

请注意,我们只是根据 connectConsoleWindow 的功能命名方法。你将它错误地命名为createConsoleWindow,然后评论它的行为——也就是说,它不创造任何东西,而只是连接东西。 这是一个可怕的反模式,必须被视为即时代码审查失败。在可行的情况下,根据他们所做的事情命名(在此处)。使代码自我记录。

最后,这就是您应该如何实例化诸如计时器等长寿命成员的方式 - 不使用原始指针和易受人为错误影响的显式内存分配:

class ConsoleWindowModel : public QAbstractTableModel

   Q_OBJECT
   unsigned int m_rowCount;
   QTimer m_controllerTimer;
   void updateController()
   
      beginInsertRows(QModelIndex(), m_rowCount, m_rowCount);
      m_rowCount++;
      endInsertRows();
   
public:
   ConsoleWindowModel(QObject *parent = 0)
      : QAbstractTableModel(parent)
      , m_rowCount(0) 
      connect(&m_controllerTimer, &QTimer::timeout, this, &ConsoleWindowModel::updateController);
      m_controllerTimer.start(2000);
   
   [...]

【讨论】:

非常感谢您提供如此出色的答案并花时间传递我所希望的最佳实践。 让我不同意你关于“Q_SLOT 与插槽”的观点:即使为单个插槽添加前缀也不会提高可读性,它只会增加视觉混乱,更不用说过多的 UPPERCASES_WITH_UNDERSCORES... @vines 这是一个有争议的问题,使用 Qt 5,除非您希望在运行时通过名称访问它们,否则您不需要声明插槽。我已经相应地更新了答案。

以上是关于Qt5 连接导致 Unhandled Exception 或 HEAP CORRUPTION的主要内容,如果未能解决你的问题,请参考以下文章

QT5线程关闭

ABP vNext报错:An unhandled exception occurred while processing the request.求大佬解惑??

解决YUM报错except KeyboardInterrupt,

解决用try except 捕获assert函数产生的AssertionError异常时,导致断言失败的用例在测试报告中通过的问题

在使用ubuntu16.04时候 qt4与qt5冲突导致的问题

Python学习篇 Python中的常见的BUG及异常处理