记录一次 QT 程序崩溃及解决
一、问题描述
最近在写程序时产生了一个 BUG,具体的情况描述如下:
- QT 版本: 5.15.2
- C++ 版本: MSVC 2005 64bit
- OS 版本: Windows10 22H2
软件运行后的截图如下(图片已注释相关内容):
具体问题表现为:
当软件初次运行后,如果控件 IReadListView 中没有任何数据(也从未加载过数据),此时点击“删除选中”按钮(IDelOneTaskBtn),程序就会突然卡死并出现崩溃。但是,当软件的 IReadListView 中加载过一次数据后(就是添加过内容并全部删除),再次点击“删除选中”按钮却并不会出现程序崩溃及其它任何异常问题。
具体发生奔溃的代码如下展示(DEBUG 定位问题出现在第 7 行代码):
1 |
|
QT 给出的问题描述情况如下列举:
- 在 DEBUG 调试模式下 QT 抛出异常警告:
error: Debugger encountered an exception: Exception at 0x7ffe1254d73a, code: 0xc0000005: read access violation at: 0x8, flags=0x0 (first chance)
- 在 DEBUG 调试模式下 QT 会将程序阻塞在下图所示位置:
- 在运行模式下 QT 会给出如下运行输出:
二、问题解决
经过 DEBUG 调试,得到的信息如下:
运行到第 4 行代码时:
运行到第 7 行代码时:
如果再接着运行就会出现程序崩溃。
可以很明显的看出来,在第七行代码处 SelectionModel
变量指向的地址为 0x0
为空值。也就是说,变量 SelectionModel
并没有接收到具体的值,也就是一个空指针。所以,可以认定此次问题是由于空指针的访问问题引起的。
那么,解决该问题的方式也很简单。我们只需要在使用变量 SelectionModel
之前对其进行检查,如果其为空指针,那么就应该直接阻断函数运行(也可以抛出异常配合 try...catch...
解决)。
下面是修正代码:
1 |
|
三、问题研究
到此为止,都还顺利(因为这个问题本身并不难),之所以要记录这次解决的过程,原因有二:
- 这个问题虽然简单,但是初学者还是要注意,空指针/野指针一直都是 CPP 编程中非常棘手的问题。
- 我想知道为什么 IReadListView 加载过一次数据后就可以正常运行了,即使数据已经被清除干净。
最后经过查阅各方资料,可以总结第二个现象出现的原因:
ui->IReadListView->selectionModel()
返回的 QItemSelectionModel
对象是由 Qt 在内部管理的,它通常在视图被创建时被创建和设置。一般情况下,不需要手动为视图设置选择模型,而是让 Qt 自动管理。
而在添加数据后问题消失,这可能是因为在添加数据时,Qt 内部完成了与选择模型的连接。这样一来,在调用ui->IReadListView->selectionModel()
时,就不再返回空指针。
然而,当删除所有数据时,Qt 可能仍然保留了选择模型,因此在调用 hasSelection()
时不会出现问题。这种情况下,虽然视图内没有实际的选中项,但选择模型仍然存在,因此不会导致空指针访问。
补充(关于 Qt 内部完成了与选择模型的连接):
在Qt中,视图类(如QListView、QTreeView等)通常有一个与之相关联的选择模型(QItemSelectionModel)。选择模型负责跟踪视图中的选择项,以及提供有关选择的信息。
当向视图中添加数据时,Qt 会在内部管理与这些数据相关的选择模型。这是为了确保当选择或取消选择视图中的项时,选择模型能够正确地跟踪这些更改。当调用 ui->IReadListView->selectionModel()
时,实际上是在请求与 ui->IReadListView
相关联的选择模型的指针。如果在添加数据时,Qt 内部已经创建并与选择模型连接,那么这个指针将不会是空的。
在删除所有数据时,选择模型可能仍然存在,即使视图中没有实际的选中项。这是因为选择模型是与视图相互独立的对象,它可能被保留下来以便在将来重新使用。关于问题的根本在于,当删除所有数据时,应用逻辑可能需要更显式地处理选择模型的状态。
继续深究可以查阅 QT 关于这两个函数的定义(selectionModel
和 setSelectionModel
):
其中,在 setSelectionModel
函数的定义描述中我们可以看到这样的描述:“It is up to the application to delete the old selection model if it is no longer needed;(如果旧的选择模型不再需要,由应用程序负责删除它)”。
所以,可以推测出这个模型关联一旦产生就不会被自动回收,除非被关联的模型本身需要回收(“This will happen automatically when its parent object is deleted.”)时才会自动删除这种关联。于是,就产生了一旦加载过数据(已经建立关联),就不会再出现崩溃(关联未被清除)的现象。
至此,这个问题才算被很好的解决了!虽然我们并不清楚 QT 内部到底是怎么管理的(等有机会再去查源码再更加深入研究),但是我们研究清除了问题产生的原因和大致过程,并且已经找到了解决方案!做到了知其然并知其所以然,这已经是进步!继续加油(ง •_•)ง!
补充说明:该函数将当前选择模型设置为给定的选择模型。请注意,如果在调用此函数后调用了 setModel(),则给定的选择模型将被视图创建的一个替代。注意:如果旧的选择模型不再需要,由应用程序负责删除它;即,如果它没有被其他视图使用。当其父对象被删除时,这将自动发生。但是,如果它没有父对象,或者父对象是一个长寿命的对象,最好调用其 deleteLater() 函数来显式删除它。参见 selectionModel()、setModel() 和 clearSelection()。