C ++析构函数调用数组索引?在非线程安全的引用计数对象上崩溃

时间:2014-08-21 22:08:07

标签: c++ arrays thread-safety destructor smart-pointers

以下代码(来自Apache Tuscany SDO C ++)偶尔(实际上非​​常罕见)导致后续崩溃,我不明白发生了什么。以下语句位于DataObjectImpl.cpp中(请参阅下面的堆栈):

PropertyImpl* DataObjectImpl::getPropertyImpl(unsigned int index)
{
...
904 PropertyList props = getType().getProperties();
905 if (index < props.size())
906 {
907   return (PropertyImpl*)&props[index];
...

导致下面的堆栈(上面和下面的所有省略的框架看似合理):

Note: #11 libtuscany_sdo.dll!std::vector<>::~vector<>                                [c:\program files\microsoft visual studio 9.0\vc\include\vector:559]
Note: #12 libtuscany_sdo.dll!commonj::sdo::PropertyList::~PropertyList               [y:\external\tuscany\src\runtime\core\src\commonj\sdo\propertylist.cpp:60]
Note: #13 libtuscany_sdo.dll!commonj::sdo::DataObjectImpl::getPropertyImpl           [y:\external\tuscany\src\runtime\core\src\commonj\sdo\dataobjectimpl.cpp:907]
Note: #14 libtuscany_sdo.dll!commonj::sdo::DataObjectImpl::getSDOValue               [y:\external\tuscany\src\runtime\core\src\commonj\sdo\dataobjectimpl.cpp:3845]

实际的问题是 - 为什么PropertyList的析构函数叫做??

如上所述,堆栈看起来不错,否则也是向量析构函数,因为PropertyList有一个成员std :: vector&lt; PropertyImplPtr&gt; plist中; PropertyList的数组索引运算符只调用plist成员的数组索引。

而且,对我来说更令人费解的是,为什么偶尔会发生这种情况...... 很多人!!

编辑:不幸的是我的原始问题是错误的(我的误解):是的,析构函数的调用是正确的,正如回答/评论/解释。 我进一步调查了这个问题并且我非常肯定能够理解真正的问题是什么 - 请看下面自己的答案,可能它可以帮助其他人......为了涵盖这两个主题,我略微更改了标题并扩展了标签列表。希望好的......

3 个答案:

答案 0 :(得分:2)

  • 变量PropertyList props具有局部范围(即成员函数DataObjectImpl::getPropertyImpl的范围)。

  • 在成员函数DataObjectImpl::getPropertyImpl中的return语句之后,变量PropertyList props的析构函数被激发,因此对象props被销毁。

  • 因此,您将返回已销毁对象的地址。访问已经销毁的对象会导致未定义的行为。

解决方案:

返回要返回的对象的克隆:

std::unique_ptr<PropertyImpl> 
DataObjectImpl::getPropertyImpl(unsigned int index) {
  PropertyList props = getType().getProperties();
  if (index < props.size()) {
    return std::unique_ptr<PropertyImpl>(new PropertyImpl(props[index]));
  ...

答案 1 :(得分:1)

PropertyList propsgetPropertyImpl函数中的局部变量。 getPropertyImpl完成后,props会自动销毁,并调用PropertyList的析构函数。显然这是你看到的析构函数。

如果getType().getProperties()通过引用返回其结果,那么您可能应该这样做

PropertyList &props = getType().getProperties();

但如果它按值返回PropertyList,那么就无法做到

return (PropertyImpl*)&props[index];

props声明为局部变量。局部变量在函数出口处被杀死,返回的指针变为无效(使用您描述的症状:有时它“起作用”,有时它不起作用。)

顺便说一句,为什么要投射&props[index]的结果? &props[index]的原始类型是什么?

答案 2 :(得分:0)

我原来的问题被误解了,我原以为删除PropertyList会导致我的编程崩溃。实际上这的情况,但实际原因是,我认为,这里(以防万一,有人关心):

我的编程运行多个线程做同样的事情。它正在大量使用SDO对象,而在许多地方又使用了RefCountingPtr机制。通常这很好用,但正如最初所说的那样,偶尔我的程序会崩溃。 C ++ SDO的一个概念是它是基于类型的,类型是使用XSD定义的,一开始我的prog将XSD加载到所有线程之间共享的全局类型repo中(因为我在想,这会发生一次,之后是类型是只读的,所以这样做是安全的。类型具有属性列表,每个属性由PropertyImplPtr表示,而PropertyImplPtr又是RefCountingPtr。看起来像这样:

class TypeImpl {
...
std::vector<PropertyImplPtr> props;
...

typedef RefCountingPointer<PropertyImpl> PropertyImplPtr;

现在,当PropertyList在原始帖子中被删除时,PropertyImplPtr的包含向量将被释放,因此引用计数减少,如果为零,则对象将自行删除。恰好这种情况发生在我的崩溃中,只是我省略了堆栈的那部分因为我认为它不重要:

Note: # 0 replace_operator_delete                                                    [d:\drmemory_package\common\alloc_replace.c:2524]
Note: # 1 libtuscany_sdo.dll!commonj::sdo::PropertyImpl::`vector deleting destructor'
Note: # 2 libtuscany_sdo.dll!commonj::sdo::RefCountingObject::releaseRef             [y:\external\tuscany\src\runtime\core\src\commonj\sdo\refcountingobject.cpp:71]
Note: # 3 libtuscany_sdo.dll!commonj::sdo::RefCountingPointer<>::~RefCountingPointer<> [y:\external\tuscany\src\runtime\core\src\commonj\sdo\refcountingpointer.h:146]
Note: # 4 libtuscany_sdo.dll!commonj::sdo::RefCountingPointer<>::`scalar deleting destructor'

那么,现在的问题是refcount如何可能为零?看起来好像RefCounting实现不是线程安全的,例如:

template<class T>
void RefCountingPointer<T>::init()
{
    if (pointee == 0) return;
    pointee->addRef();
}
...
template<class T>
/*SDO_API*/ RefCountingPointer<T>& RefCountingPointer<T>::operator=(const RefCountingPointer& rhs)
{
    if (pointee != rhs.pointee)
    {
        T *oldP = pointee;
        pointee = rhs.pointee;
        init();
        if (oldP) oldP->releaseRef();
    }
    return *this;
}

这里,pointee获得一个额外的引用,而refcount将在init()中递增。如果现在另一个线程想要删除这样的对象,它可能会这样做,因为refcount仍然是1。