1.情况
最近在对之前写的一个软件进行重构,重构完之后,给到现场去用。
现场反馈说,软件操作操作着,就崩溃了。
由于软件在崩溃时会保留dump文件的(实现过程看这里:【Qt下生成pdb文件,并在exe崩溃时生成dmp文件,且由dmp查询崩溃原因】),通过dump文件,能够得到的信息为:
> Qt5Core.dll!QtSharedPointer::ExternalRefCountData::getAndRef(const QObject * obj=0x0000020c9821aa70) 行 1398 C++ 已加载符号。
[内联框架] Qt5Qml.dll!QV4::QQmlQPointer<QObject>::init(QObject *) 行 223 C++ 已加载符号。
[内联框架] Qt5Qml.dll!QV4::Heap::QObjectWrapper::init(QObject *) 行 83 C++ 已加载符号。
[内联框架] Qt5Qml.dll!QV4::MemoryManager::allocate(QObject *) 行 245 C++ 已加载符号。
Qt5Qml.dll!QV4::QObjectWrapper::create(QV4::ExecutionEngine * engine=0x0000020be3868650, QObject * object=0x0000020c9821aa70) 行 694 C++ 已加载符号。
Qt5Qml.dll!QV4::QObjectWrapper::wrap_slowPath(QV4::ExecutionEngine * engine=0x0000020be3868650, QObject * object=0x0000020c9821aa70) 行 599 C++ 已加载符号。
[内联框架] Qt5Qml.dll!QV4::QObjectWrapper::wrap(QV4::ExecutionEngine * object=0x0000020c9821aa70, QObject *) 行 222 C++ 已加载符号。
Qt5Qml.dll!loadProperty(QV4::ExecutionEngine * v4=0x0000020be3868650, QObject * object=0x0000020be86135b0, const QQmlPropertyData & property) 行 139 C++ 已加载符号。
Qt5Qml.dll!QV4::QObjectWrapper::lookupGetterImpl<unsigned __int64 <lambda>(void)>(QV4::Lookup * lookup=0x0000020be4d81c20, QV4::ExecutionEngine * engine=0x0000020be3868650, const QV4::Value & object, bool useOriginalProperty, QV4::QObjectWrapper::lookupGetter::__l2::unsigned __int64 <lambda>(void) revertLookup=unsigned __int64 <lambda>(void){...}) 行 262 C++ 已加载符号。
Qt5Qml.dll!QV4::QObjectWrapper::lookupGetter(QV4::Lookup * lookup, QV4::ExecutionEngine * engine, const QV4::Value & object) 行 896 C++ 已加载符号。
0000020c02a00b3f() 未知 未加载任何符号。
这些全是qml里面的东西,又不给从c++到qml的调用堆栈,哪怕只是qml代码的调用堆栈也好。不知道怎么定位好了。
2.debug
然后我就开始漫长的debug了。
首先,按照现场人员的操作步骤,我在我的电脑上也复现了这个崩溃。那就说明这个应该是和设备无关的。
然后我在QtCreator上打开工程,进入debug模式,然后按照指定的步骤操作,也得到了类似的堆栈信息:
虽然信息长了一些,但是还是没有qml代码的调用堆栈。但是,看看灰色那里
QQuickAbstractButton::clicked
,那就是在点击某个Button之后触发的这个错误,而在操作的最后一步,的确是按了某个按钮。
然后我就把这个按钮的onclicked槽函数屏蔽掉,然后再测试就没问题了。那就说明是这个槽函数里面的某行代码出问题了。
接着,我就一部分一部分、一行一行代码地注释/取消注释地测试,最后定位到了那一行。
定位到那一行后,后面又针对这行代码反复测试、联系前后文上下文来猜想,然后对这行代码研究了一两天后,发现在某些情况下,代码中访问的某个item的属性可能为野指针。
然后单独写了个测试程序来测试,好家伙,报错的堆栈结构一模一样,那就确定是这个野指针的问题了。
3.测试程序
这里我简单介绍一下我后面用的测试程序,方便大家复现。
在qml工程中,添加两个c++类:JobItem
、ToolItem
。其中JobItem
有个属性curItem
,其类型为ToolItem *
。如下图所示
然后在JobItem
的构造函数中,对属性curItem
赋值
接着把这两个类注册到qml中
qmlRegisterType<JobItem>("ZyCppItems", 1, 0, "JobItem");
qmlRegisterType<ToolItem>("ZyCppItems", 1, 0, "ToolItem");
然后在qml中创建,并测试:
点击按钮,就得到了前面的报错了。
4.解决方案
使用QPointer。
不改变属性的类型,但是稍微改变一下该属性在C++这边的的存储形式:
这样一来,因为QPointer会通过一定的机制(详情请查看这里:【Qt之QPointer】)根据对象是否被析构而动态改变自己的值,我们就不用担心会访问到野指针了。在指针被delete后,qml访问到的属性自动变成null了:
参考:
【Qt之QPointer】