QMAP导致崩溃问题
在使用QMAP的过程中发现导致程序崩溃的神奇问题,很有代表意义,所以把分析过程写出来,与大家共同学习。
一、问题描述
while(1)
{
QMapIterator<QString, int> i(map);
while (i.hasNext()) {
i.next();
qDebug() << i.key() << "1: " << i.value();
}
QMap<QString, int>::const_iterator i2 = map.constBegin();
while (i2 != map.constEnd()) {
qDebug() << i2.key() << "2: " << i2.value();
i2;
}
QMap<QString, int>::iterator i3 = map.find(QString::number((int)this));
if( i3 != map.end() && i3.key() == QString::number((int)this) )
{
qDebug() << i3.key() << "3: " << i3.value();
}
}
上述代码中map为全局变量,多线程运行时会出现coredump
1、反馈上述3段遍历map的代码,屏蔽任何一段都不会coredump。(后来发现不对,屏蔽第2段时也会coredump)。
2、对全部代码加读锁时依然coredump,加写锁时正常。
3、根据代码分析应该没有对map的写操作,但是通过现象分析可能有对map的写操作。
二、源码分析
所以先看QMap的源码有没有对map的写操作,先看QMapIterator的源码
qiterator.h:
#define Q_DECLARE_MUTABLE_ASSOCIATIVE_ITERATOR(C) \
\
template <class Key, class T> \
class QMutable##C##Iterator \
{ \
typedef typename Q##C<Key,T>::iterator iterator; \
typedef typename Q##C<Key,T>::const_iterator const_iterator; \
typedef iterator Item; \
Q##C<Key,T> *c; \
iterator i, n; \
inline bool item_exists() const { return const_iterator(n) != c->constEnd(); } \
public: \
inline QMutable##C##Iterator(Q##C<Key,T> &container) \
: c(&container) \
{ i = c->begin(); n = c->end(); } \
inline QMutable##C##Iterator &operator=(Q##C<Key,T> &container) \
{ c = &container; i = c->begin(); n = c->end(); return *this; } \
inline void toFront() { i = c->begin(); n = c->end(); } \
inline void toBack() { i = c->end(); n = c->end(); } \
inline bool hasNext() const { return const_iterator(i) != c->constEnd(); } \
inline Item next() { n = i ; return n; } \
inline Item peekNext() const { return i; } \
inline bool hasPrevious() const { return const_iterator(i) != c->constBegin(); } \
inline Item previous() { n = --i; return n; } \
inline Item peekPrevious() const { iterator p = i; return --p; } \
inline void remove() \
{ if (const_iterator(n) != c->constEnd()) { i = c->erase(n); n = c->end(); } } \
inline void setValue(const T &t) { if (const_iterator(n) != c->constEnd()) *n = t; } \
inline T &value() { Q_ASSERT(item_exists()); return *n; } \
inline const T &value() const { Q_ASSERT(item_exists()); return *n; } \
inline const Key &key() const { Q_ASSERT(item_exists()); return n.key(); } \
inline bool findNext(const T &t) \
{ while (const_iterator(n = i) != c->constEnd()) if (*i == t) return true; return false; } \
inline bool findPrevious(const T &t) \
{ while (const_iterator(i) != c->constBegin()) if (*(n = --i) == t) return true; \
n = c->end(); return false; } \
};
这里对迭代器封装了一下,使用了QMap的原始指针,没看出问题。但是在调试过程中发现,实际是进到下面代码
#define Q_DECLARE_ASSOCIATIVE_ITERATOR(C) \
\
template <class Key, class T> \
class Q##C##Iterator \
{ \
typedef typename Q##C<Key,T>::const_iterator const_iterator; \
typedef const_iterator Item; \
Q##C<Key,T> c; \
const_iterator i, n; \
inline bool item_exists() const { return n != c.constEnd(); } \
public: \
inline Q##C##Iterator(const Q##C<Key,T> &container) \
: c(container), i(c.constBegin()), n(c.constEnd()) {} \
inline Q##C##Iterator &operator=(const Q##C<Key,T> &container) \
{ c = container; i = c.constBegin(); n = c.constEnd(); return *this; } \
inline void toFront() { i = c.constBegin(); n = c.constEnd(); } \
inline void toBack() { i = c.constEnd(); n = c.constEnd(); } \
inline bool hasNext() const { return i != c.constEnd(); } \
inline Item next() { n = i ; return n; } \
inline Item peekNext() const { return i; } \
inline bool hasPrevious() const { return i != c.constBegin(); } \
inline Item previous() { n = --i; return n; } \
inline Item peekPrevious() const { const_iterator p = i; return --p; } \
inline const T &value() const { Q_ASSERT(item_exists()); return *n; } \
inline const Key &key() const { Q_ASSERT(item_exists()); return n.key(); } \
inline bool findNext(const T &t) \
{ while ((n = i) != c.constEnd()) if (*i == t) return true; return false; } \
inline bool findPrevious(const T &t) \
{ while (i != c.constBegin()) if (*(n = --i) == t) return true; \
n = c.constEnd(); return false; } \
};
这里使用了QMap的对象,使用了拷贝构造
qmap.h
template <class Key, class T>
inline QMap<Key, T>::QMap(const QMap<Key, T> &other)
{
if (other.d->ref.ref()) {
d = other.d;
} else {
d = QMapData<Key, T>::create();
if (other.d->header.left) {
d->header.left = static_cast<Node *>(other.d->header.left)->copy(d);
d->header.left->setParent(&d->header);
d->recalcMostLeftNode();
}
}
}
template <class Key, class T>
class QMap
{
typedef QMapNode<Key, T> Node;
QMapData<Key, T> *d;
......
}
struct Q_CORE_EXPORT QMapDataBase
{
QtPrivate::RefCount ref;
int size;
QMapNodeBase header;
QMapNodeBase *mostLeftNode;
....
}
QMap有一个d指针,d指针对象中有一个引用计数器,和红黑树使用的一些指针。所以d指针实际有了智能指针的功能。怀疑是智能指针跨线程使用问题,分析代码正常。顺便分析一下QMap的代码看看d指针。
inline QMap() Q_DECL_NOTHROW : d(static_cast<QMapData<Key, T> *>(const_cast<QMapDataBase *>(&QMapDataBase::shared_null))) { }
const QMapDataBase QMapDataBase::shared_null = { Q_REFCOUNT_INITIALIZE_STATIC, 0, { 0, 0, 0 }, 0 };
QMap构造时d指针为空
template <class Key, class T>
Q_INLINE_TEMPLATE typename QMap<Key, T>::iterator QMap<Key, T>::insert(const Key &akey, const T &avalue)
{
detach();
Node *n = d->root();
.....
}
inline void detach() { if (d->ref.isShared()) detach_helper(); }
template <class Key, class T>
Q_OUTOFLINE_TEMPLATE void QMap<Key, T>::detach_helper()
{
QMapData<Key, T> *x = QMapData<Key, T>::create();
if (d->header.left) {
x->header.left = static_cast<Node *>(d->header.left)->copy(x);
x->header.left->setParent(&x->header);
}
if (!d->ref.deref())
d->destroy();
d = x;
d->recalcMostLeftNode();
}
第一次Insert时会调用detach,此时d指针的引用计数为-1,所以会调用detach_helper,对d指针进行初始化,初始化后引用计数为1。以后insert都不会再调用detach_helper。
使用QMutableMapIterator会调用
#define Q_DECLARE_MUTABLE_ASSOCIATIVE_ITERATOR©
这里使用map的原始指针,测试没有问题
三、调试代码
在用VS调试过程中偶然发现map的d指针内容会改变,怀疑有地方对map做了写操作。通过调试发现find后map的d指针内容会改变。查看find代码
template <class Key, class T>
Q_INLINE_TEMPLATE typename QMap<Key, T>::iterator QMap<Key, T>::find(const Key &akey)
{
detach();
Node *n = d->findNode(akey);
return iterator(n ? n : d->end());
}
inline void detach() { if (d->ref.isShared()) detach_helper(); }
template <class Key, class T>
Q_OUTOFLINE_TEMPLATE void QMap<Key, T>::detach_helper()
{
QMapData<Key, T> *x = QMapData<Key, T>::create();
if (d->header.left) {
x->header.left = static_cast<Node *>(d->header.left)->copy(x);
x->header.left->setParent(&x->header);
}
if (!d->ref.deref())
d->destroy();
d = x;
d->recalcMostLeftNode();
}
可以看出如果map引用计数大于1时,会执行detach_helper,这里对map的d指针作了一次拷贝操作。改变了d的地址和内容。
inline ~QMap() { if (!d->ref.deref()) d->destroy(); }
把这段代码放到单线程运行,分析其运行过程。
while(1)
{
QMapIterator<QString, int> i(map);
while (i.hasNext()) {
i.next();
qDebug() << i.key() << "1: " << i.value();
}
//执行第一段后map的d指针的引用计数变为2
QMap<QString, int>::const_iterator i2 = map.constBegin();
while (i2 != map.constEnd()) {
qDebug() << i2.key() << "2: " << i2.value();
i2;
}
QMap<QString, int>::iterator i3 = map.find(QString::number((int)this));
//执行find后,因为map的d指针的引用计数变为2,会执行detach_helper,map的d指针被重新拷贝赋值,它的引用计数初始化为1,地址和内容都改变。原来i中的c的d指针(即原map的d指针)的引用计数变为1,内容不变。
if( i3 != map.end() && i3.key() == QString::number((int)this) )
{
qDebug() << i3.key() << "3: " << i3.value();
}
}
//全部执行完后局部变量i析构,i中的c析构,会释放c的d指针的所有内容,即原map的所有内容被释放。
//下一循环使用新的map来构造i。
所以单线程没有问题,多线程时如果原map析构后,有其它线程使用其内容就会coredump
四、修改方法
1、QMapIterator换成QMutableMapIterator 使用QMap的指针,避免了拷贝(引用计数)
2、find换成constFind 避免了使用detach
发现begin和end也会做detach操作,所有带有detach操作的方法跨线程使用时都可能出现野指针的情况。
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhihfakc
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
excel图片置于文字下方的方法
PHP中文网 06-27 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
微信提示登录环境异常是什么意思原因
PHP中文网 04-09 -
微信运动停用后别人还能看到步数吗
PHP中文网 07-22 -
微信人名旁边有个图标有什么用
PHP中文网 03-11