在使用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指针的引用计数变为2QMap<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操作的方法跨线程使用时都可能出现野指针的情况。

QMAP导致崩溃问题分析相关推荐

  1. c++的lambda使用注意事项,可能导致的崩溃问题分析

    Lambda表达式是现代C++的一个语法糖,挺好用的.但是如果使用不当,会导致内存泄露或潜在的崩溃问题.这里总结下Lambda表达式的使用注意事项,避免在使用中的一些陷阱. Lambda介绍 &quo ...

  2. C++ 常见崩溃问题分析

    一.前言 从事自动化测试平台开发的编程实践中,遭遇了几个程序崩溃问题,解决它们颇费了不少心思,解决过程中的曲折和彻夜的辗转反侧却历历在目,一直寻思写点东西,为这段难忘的经历留点纪念,总结惨痛的教训带来 ...

  3. android崩溃系列-崩溃原理分析

    文章目录 结论 源码分析 使用 结论 在java默认的异常处理机制中,是没有崩溃退出这个说法的,而在android中的RuntimeInit对其拦截并且处理. 源码分析 首先关注Thread类中的di ...

  4. Melis系统崩溃问题分析以及解决思路

    Melis系统崩溃问题分析以及解决思路 在使用Melis 系统开发时,会经常遇到系统崩溃死机的问题,对于此类问题大家总是避之不及, 分析原因, 一方面是此类问题涉及到CPU异常模式架构,需要用户对ar ...

  5. cocos creater 热更重启导致崩溃

    cocos creater 热更重启导致崩溃 知识点 jsb_websocket_server.cpp 游戏中用到的 socket inspector_socket_server.cc V8引擎中 用 ...

  6. Android Bitmap转换WebP图片导致损坏的分析及解决方案

    Android Bitmap转换WebP图片导致损坏的分析及解决方案 参考文章: (1)Android Bitmap转换WebP图片导致损坏的分析及解决方案 (2)https://www.cnblog ...

  7. ThreadLocal使用时因线程复用导致数据混乱分析

    ThreadLocal使用时因线程复用导致数据混乱分析 本文主要阐述使用ThreadLocal遇到数据混乱情况下的具体分析和解决过程 ThreadLocal原理 网上有很多介绍,不做详细介绍主要有四个 ...

  8. PyQt5随笔:PyQt5 程序在开机自启动时读取文件出错导致崩溃解决办法

    PyQt5随笔:PyQt5 程序在开机自启动时读取文件出错导致崩溃解决办法 1.前言 最近在写一个 Python+pyqt5 小项目,在改善过程中想添加一个日志记录,我是打算用txt 文件记录就好,操 ...

  9. iOS崩溃日志分析-b

    1名词解释 1.1. UUID 一个字符串,在iOS上每个可执行文件或库文件都包含至少一个UUID,目的是为了唯一识别这个文件. 1.2. dwarfdump 苹果提供的命令行工具,其中一些功能就是查 ...

最新文章

  1. android数据库isnull,Android中SQLite数据库知识点总结
  2. ubuntu13.04下安装jdk7
  3. centos下安装PHP的IDE,如何在 CentOS 8 上安装和使用 PHP 编辑器
  4. 94. Binary Tree Inorder Traversal 二叉树的中序遍历
  5. Replace Array with Object(以对象取代数组)
  6. java 判断 框架类型_第10章-验证框架 --- 验证器类型
  7. Error: EBUSY: resource busy or locked, lstat ‘D:\DumpStack.log.---基于Vue的uniapp手机端_前端UI_uview工作笔记004
  8. android 速度传感器,Android实战技巧之四十二:加速度传感器
  9. 浏览器中json的使用 与jquery无关 json2.js
  10. java string查找_Java lastIndexOf() 方法
  11. 各类原版系统下载:在MSDN下载Windows、MacOS、Linux原版系统镜像
  12. DayDayUp:计算机技术与软件专业技术资格证书之《系统集成项目管理工程师》课程讲解之十大知识领域之4辅助—项目采购管理
  13. 雅虎助手是如何自杀式攻击360安全卫士的?雅虎助手,3721是什么恶心人的--(部份转贴)
  14. 商城客服功能-------环信即时通讯
  15. 手机误删的照片怎么恢复?恢复方法分享
  16. 机器学习-Sklearn-07(无监督学习聚类算法KMeans)
  17. Flutter | 和小老弟一起学资源管理
  18. Echarts:10-7-4:混合图(降水量蒸发量平均温度)
  19. Three.js--》实现3d官网模型展示
  20. 面向对象学不会看不懂?一文详解面向对象知识点总结

热门文章

  1. maven仓库查找jar包
  2. oracle 外键约束 权限,ORACLE外键约束(FORIGEN KEY)
  3. Linux开发之Makefile简明教程及示例
  4. C#Assembly的使用
  5. 中国历史上最早的牛人
  6. 生产者消费者模式(1)
  7. 影评之《教父》维托·柯里昂
  8. java中的熔断机制_SpringCloud- 第八篇 Hystrix熔断机制(五)
  9. Dominated Subarray
  10. 网赚项目:怎么做好一个副业,视频号的引流及变现模式