使用QString会在QMap::remove之后导致崩溃

Using QString causes crash after QMap::remove

本文关键字:崩溃 之后 remove 会在 QMap 使用 QString      更新时间:2023-10-16

我有以下代码:

class NamedObjectContainer {
//...
QMap<QString, SomeStruct> mUsed;
//...
};
const StoredObject* NamedObjectContainer::use(const QString& name, const QString& userId)
{
qDebug()<<userId;
mUsed.remove(userId);
qDebug()<<userId;
//...
}

在这里,我试图通过键(userId)从QMap中删除元素。元素已正确移除。但令人惊讶的是,它在QMap::remove之后打印userId时崩溃了。

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0xb5b2c6c0 (LWP 24041)]
0xb5fe899c in memcpy () from /lib/i686/nosegneg/libc.so.6
(gdb) where
#0  0xb5fe899c in memcpy () from /lib/i686/nosegneg/libc.so.6
#1  0xb7263246 in QString::append () from /home/osmin/stand_cl_dir/Qt4_x86-linux/lib   /libQtCore.so.4
#2  0xb72b6641 in ?? () from /home/osmin/stand_cl_dir/Qt4_x86-linux/lib/libQtCore.so.4
#3  0xb72b218b in QTextStream::operator<< () from /home/osmin/stand_cl_dir/Qt4_x86-linux/lib/libQtCore.so.4
#4  0xb6524740 in QDebug::operator<< () from /usr/lib/libqxmlrpc.so.1
#5  0xb62b5cc0 in tabexchange::NamedObjectContainer::use (this=0x9e2fb08, name=@0xbffe85e4, userId=@0xa12b780) at namedcontainer.cpp:208

是什么原因导致了问题?我使用的是Qt 4.4.3

要详细说明@TI的评论。。。

QString是一种隐式共享类型。每个由QString对象组成的新副本都会增加引擎盖下的引用计数,当计数为零时,它就会被销毁。

这里可能发生的情况是,有一个初始化例程生成了一个QString实例,将其作为密钥传递,映射生成了一份副本。(这不会复制数据,只是增加共享计数。)然后初始化例程销毁了它的实例,所以剩下的唯一共享实例是存储在映射中的共享计数为1的实例。

稍后,您可能会使用类似QMap::iterator::key()的东西来获取映射中字符串键的const引用,作为userId传入。这不会创建任何新的QString实例来添加到共享计数中,而是指向映射所拥有的实例。因此,当地图放开它时……它被破坏了,现在userId是一个悬挂的引用。

(注意:你没有说明SomeStruct中有什么。但如果通过它可以到达与密钥匹配的字符串的实例,该实例将在映射值的SomeStruct被销毁时被销毁,那么传递对userId这样的字符串的引用可能会导致类似的问题。)

隐式共享带来的一件事是,有时它会隐藏这种性质的bug——如果没有隐式共享,这些bug会变得更加明显。然而,它使解决方案"便宜":当您提取要传入的密钥时,将其复制到本地变量实例中。。。并将该变量对的const引用传递到此例程。这实际上不会复制数据,但会使userId安全,因为还会有一个共享计数使其保持活动状态。

这有助于实现更普遍的好协议:将引用类型传递给例程应该意味着您可以保证被引用对象在所调用函数的整个运行时的生存期。如果有任何疑问,请制作一个副本并传递对副本的引用。

(注意:以后,尝试使用简单、自包含、正确的示例格式,包括添加和删除,这可以更容易地找到确凿的证据。没有它,我们只能对问题进行有根据的猜测……它可能完全是由程序中的其他原因引起的!)