初识Qt,进行开发时遇到一个崩溃问题 简单整理
1:问题描述如下,结构体中QString成员,然后对结构体调用了memset导致问题:
2:问题分析,加断点调试的方式可以明确分析到行数
可以明确看出,初始化时成员变量的值为空的字符串,然后执行memset后,该成员地址无法访问。
3:找源码进行分析一下
3.1.这里首先测试一下string等其他成员操作,没有出现问题。
3.2 分析QString的源码,了解原因(qtbase源码分析)。
简化后源码如下:
const QArrayData QArrayData::shared_null[2] = {{ -1, 0, 0, 0, sizeof(QArrayData) }, } inline QString::QString() noexcept : d(Data::sharedNull()) {} inline QString::~QString() { if (!d->ref.deref()) Data::deallocate(d); } class Q_CORE_EXPORT QString { public: typedef QStringData Data; //基类是 QArrayData inline QString() noexcept; private: Data *d; //基类是 QArrayData 控制引用计数 public: typedef Data * DataPtr; inline DataPtr &data_ptr() { return d; } }; QString::QString(const QChar *unicode, int size) { if (!unicode) { d = Data::sharedNull(); } else { if (size < 0) { size = 0; while (!unicode[size].isNull()) ++size; } if (!size) { d = Data::allocate(0); } else { d = Data::allocate(size + 1); Q_CHECK_PTR(d); d->size = size; memcpy(d->data(), unicode, size * sizeof(QChar)); d->data()[size] = '\0'; } } }
第一个分析点(构造函数):
第二个分析点,真正控制QString的数据体,只能指针+数据域
实际真正的底层是QArrayData类
struct Q_CORE_EXPORT QArrayData { QtPrivate::RefCount ref; //基类实现原子变量的递增递减 int size; uint alloc : 31; uint capacityReserved : 1; qptrdiff offset; // in bytes from beginning of header void *data() { Q_ASSERT(size == 0 || offset < 0 || size_t(offset) >= sizeof(QArrayData)); return reinterpret_cast<char *>(this) + offset; } static const QArrayData shared_null[2]; static QArrayData *sharedNull() noexcept { return const_cast<QArrayData*>(shared_null); } }; //真正的申请内存 QArrayData *QArrayData::allocate(size_t objectSize, size_t alignment, size_t capacity, AllocationOptions options) noexcept { Q_ASSERT(alignment >= Q_ALIGNOF(QArrayData) && !(alignment & (alignment - 1))); if (!(options & RawData) && !capacity) { return const_cast<QArrayData *>(&qt_array_empty); } size_t headerSize = sizeof(QArrayData); if (!(options & RawData)) headerSize += (alignment - Q_ALIGNOF(QArrayData)); if (headerSize > size_t(MaxAllocSize)) return nullptr; size_t allocSize = calculateBlockSize(capacity, objectSize, headerSize, options); QArrayData *header = static_cast<QArrayData *>(::malloc(allocSize)); if (header) { quintptr data = (quintptr(header) + sizeof(QArrayData) + alignment - 1) & ~(alignment - 1); header->ref.atomic.storeRelaxed(1); header->size = 0; header->alloc = capacity; header->capacityReserved = bool(options & CapacityReserved); header->offset = data - quintptr(header); } return header; } //真正的释放内存 根据引用计数控制 void QArrayData::deallocate(QArrayData *data, size_t objectSize, size_t alignment) noexcept { // Alignment is a power of two Q_ASSERT(alignment >= Q_ALIGNOF(QArrayData) && !(alignment & (alignment - 1))); Q_UNUSED(objectSize) Q_UNUSED(alignment) Q_ASSERT_X(data == 0 || !data->ref.isStatic(), "QArrayData::deallocate", "Static data cannot be deleted"); ::free(data); }
4:为什么string成员没有问题呢(分析QLatin1String)
这里在看QString源码时看到了QLatin1String类 区分QString进行了简单查看。
首先:QLatin1String作为结构体成员,也没有问题。
4.1 简单查看QLatin1String的源码
class QLatin1String { public: Q_DECL_CONSTEXPR inline QLatin1String() noexcept : m_size(0), m_data(nullptr) {} Q_DECL_CONSTEXPR inline explicit QLatin1String(const char *s) noexcept : m_size(s ? int(strlen(s)) : 0), m_data(s) {} Q_DECL_CONSTEXPR explicit QLatin1String(const char *f, const char *l) : QLatin1String(f, int(l - f)) {} Q_DECL_CONSTEXPR inline explicit QLatin1String(const char *s, int sz) noexcept : m_size(sz), m_data(s) {} inline explicit QLatin1String(const QByteArray &s) noexcept : m_size(int(qstrnlen(s.constData(), s.size()))), m_data(s.constData()) {} //。。。其他都是基本的函数接口 Q_DECL_CONSTEXPR const char *latin1() const noexcept { return m_data; } Q_DECL_CONSTEXPR int size() const noexcept { return m_size; } Q_DECL_CONSTEXPR const char *data() const noexcept { return m_data; } Q_DECL_CONSTEXPR bool isNull() const noexcept { return !data(); } Q_DECL_CONSTEXPR bool isEmpty() const noexcept { return !size(); } private: int m_size; const char *m_data; };
4.2 简单分析 QLatin1String 就是对char*的简单封装
4.2 细节,没看到有operator= 使用赋值构造也没有问题
这是因为用了浅拷贝,内部没涉及内存的重新申请 所以也没问题。
然后因为demo中x1重新赋值,浅拷贝导致x1中data指向的const char*指向发生变化。
总结:QLatin1String只是对char*的简单封装,不涉及内存的申请,实际还是需要我们控制。
遗留:过程中看到QString中相关的编码相关函数,这块也是一个细节,待整理