• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

QMAP导致崩溃问题

武飞扬头像
段鸿潭
帮助1

在使用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
系列文章
更多 icon
同类精品
更多 icon
继续加载