c ++析构函数一塌糊涂,无法调试

时间:2009-12-04 17:50:38

标签: c++ memory-management

当我运行我的程序时,一切都很顺利。最后打印出来:

*** glibc detected *** ./streamShare: double free or corruption (fasttop): 0x08292130 ***
======= Backtrace: =========
/lib/tls/i686/cmov/libc.so.6[0xcc2ff1]
/lib/tls/i686/cmov/libc.so.6[0xcc46f2]
/lib/tls/i686/cmov/libc.so.6(cfree+0x6d)[0xcc779d]
/usr/lib/libstdc++.so.6(_ZdlPv+0x21)[0x1c86f1]
./streamShare[0x804be7f]
./streamShare[0x804be3e]
./streamShare[0x804abc0]
./streamShare[0x804a5f2]
./streamShare[0x804a1c4]
./streamShare[0x804a1d7]
./streamShare[0x804a46a]
./streamShare[0x804ba45]
./streamShare[0x804b49c]
./streamShare[0x804ac68]
./streamShare[0x804ac48]
./streamShare[0x804a676]
./streamShare[0x804a237]
./streamShare[0x8049a3f]
./streamShare[0x804d2e5]
./streamShare[0x804d34d]
/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6)[0xc6eb56]
./streamShare[0x8049361]

我检查过,它发生在一个函数返回时,程序的所有对象都会自动生成。无论如何,我没有为这些对象定义任何析构函数,我尝试使用STL容器和TR1 shared_ptr。我想一切都发生在默认的析构函数中。有没有办法知道它在哪里分手?我的意思是,我想知道哪个物体破坏会造成整个混乱。我正在使用这些容器和共享指针:

typedef std::tr1::shared_ptr<messageListener> mlsptr;

typedef std::map<const char*, mlsptr, ltstr> CONSTCHP2MSLST;

messageListener没有析构函数。其中两个载体:

std::vector<MSG> queueto1;

MSG析构函数是:

MSG::~MSG() {
    destroy();
}

void MSG::destroy() {
    if (payload != NULL)
        delete[] payload;
    payload = NULL;
    payloadLen = 0;
}

之前从未出现过问题而且我不应该这样......

任何推荐如何跟踪此问题?我很无能......

编辑:

这里是VALGRIND输出:

valgrind ./streamShare -v
==25795== Memcheck, a memory error detector
==25795== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==25795== Using Valgrind-3.5.0-Debian and LibVEX; rerun with -h for copyright info
==25795== Command: ./streamShare -v
==25795== 
==25795== Invalid free() / delete / delete[]
==25795==    at 0x402454D: operator delete(void*) (vg_replace_malloc.c:346)
==25795==    by 0x804BCC0: std::tr1::_Sp_deleter<streamShare::messageListener>::operator()(streamShare::messageListener*) const (shared_ptr.h:97)
==25795==    by 0x804BC7F: std::tr1::_Sp_counted_base_impl<streamShare::messageListener*, std::tr1::_Sp_deleter<streamShare::messageListener>, (__gnu_cxx::_Lock_policy)2>::_M_dispose() (shared_ptr.h:75)
==25795==    by 0x804AAF7: std::tr1::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() (boost_sp_counted_base.h:140)
==25795==    by 0x804A58D: std::tr1::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count() (shared_ptr.h:153)
==25795==    by 0x804A173: std::tr1::__shared_ptr<streamShare::messageListener, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr() (shared_ptr.h:358)
==25795==    by 0x804A186: std::tr1::shared_ptr<streamShare::messageListener>::~shared_ptr() (shared_ptr.h:834)
==25795==    by 0x804A405: std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> >::~pair() (stl_pair.h:68)
==25795==    by 0x804D3D0: __gnu_cxx::new_allocator<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > >::destroy(std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> >*) (new_allocator.h:115)
==25795==    by 0x804D337: std::_Rb_tree<char const*, std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> >, std::_Select1st<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > >, streamShare::ltstr, std::allocator<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > > >::_M_destroy_node(std::_Rb_tree_node<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > >*) (stl_tree.h:383)
==25795==    by 0x804D29B: std::_Rb_tree<char const*, std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> >, std::_Select1st<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > >, streamShare::ltstr, std::allocator<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > > >::_M_erase(std::_Rb_tree_node<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > >*) (stl_tree.h:972)
==25795==    by 0x804D27B: std::_Rb_tree<char const*, std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> >, std::_Select1st<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > >, streamShare::ltstr, std::allocator<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > > >::_M_erase(std::_Rb_tree_node<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > >*) (stl_tree.h:970)
==25795==  Address 0x42c3358 is 0 bytes inside a block of size 8 free'd
==25795==    at 0x402454D: operator delete(void*) (vg_replace_malloc.c:346)
==25795==    by 0x804BCC0: std::tr1::_Sp_deleter<streamShare::messageListener>::operator()(streamShare::messageListener*) const (shared_ptr.h:97)
==25795==    by 0x804BC7F: std::tr1::_Sp_counted_base_impl<streamShare::messageListener*, std::tr1::_Sp_deleter<streamShare::messageListener>, (__gnu_cxx::_Lock_policy)2>::_M_dispose() (shared_ptr.h:75)
==25795==    by 0x804AAF7: std::tr1::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() (boost_sp_counted_base.h:140)
==25795==    by 0x804A58D: std::tr1::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count() (shared_ptr.h:153)
==25795==    by 0x804A173: std::tr1::__shared_ptr<streamShare::messageListener, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr() (shared_ptr.h:358)
==25795==    by 0x804A186: std::tr1::shared_ptr<streamShare::messageListener>::~shared_ptr() (shared_ptr.h:834)
==25795==    by 0x804A405: std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> >::~pair() (stl_pair.h:68)
==25795==    by 0x804D3D0: __gnu_cxx::new_allocator<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > >::destroy(std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> >*) (new_allocator.h:115)
==25795==    by 0x804D337: std::_Rb_tree<char const*, std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> >, std::_Select1st<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > >, streamShare::ltstr, std::allocator<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > > >::_M_destroy_node(std::_Rb_tree_node<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > >*) (stl_tree.h:383)
==25795==    by 0x804D29B: std::_Rb_tree<char const*, std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> >, std::_Select1st<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > >, streamShare::ltstr, std::allocator<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > > >::_M_erase(std::_Rb_tree_node<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > >*) (stl_tree.h:972)
==25795==    by 0x804D27B: std::_Rb_tree<char const*, std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> >, std::_Select1st<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > >, streamShare::ltstr, std::allocator<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > > >::_M_erase(std::_Rb_tree_node<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > >*) (stl_tree.h:970)
==25795== 
==25795== 
==25795== HEAP SUMMARY:
==25795==     in use at exit: 0 bytes in 0 blocks
==25795==   total heap usage: 22 allocs, 30 frees, 496 bytes allocated
==25795== 
==25795== All heap blocks were freed -- no leaks are possible
==25795== 
==25795== For counts of detected and suppressed errors, rerun with: -v
==25795== ERROR SUMMARY: 8 errors from 1 contexts (suppressed: 19 from 8)

7 个答案:

答案 0 :(得分:27)

根据Valgrind输出判断,问题是shared_ptr指向的对象被删除两次。获得这种情况的一种可能性是,如果使用相同的原始指针初始化两个shared_ptr,例如:

int* p = new int(123);
shared_ptr<int> sp1(p);
shared_ptr<int> sp2(p);

shared_ptr并不神奇,它无法知道你要求它处理的对象是否已经被其他一些不相关的shared_ptr所拥有,如果你所有的都是一个原始指针。在上面的示例中,每个shared_ptr将创建自己的引用计数器,初始化为1;当它们死亡时,每个都会减少它自己的计数器,看它是0,并删除该对象,产生双重删除。我怀疑你的情况类似。如果您显示用于初始化添加到向量的shared_ptr个对象的代码,则可以对此进行验证。

[编辑] 由于现在确认这是导致崩溃的原因,让我详细说明 关于如何正确使用shared_ptr

首先,问题的本质。编写shared_ptr的方式,它适用于任何C ++类型,并提供引用计数语义。显而易见的问题是大多数类型都没有提供任何空间来存储引用计数器(例如考虑shared_ptr<int> - “int内部没有额外的空间”。要解决此问题,请为每个共享对象分配一个包含引用计数器的单独内存块。每当您从原始指针创建shared_ptr时,都会执行此操作。 shared_ptr对象本身然后存储原始的原始指针,以及指向引用计数器的指针(这就是为什么它比原始指针更“胖”,可以用sizeof轻松检查)。当您从另一个shared_ptr创建一个shared_ptr时(使用复制构造函数或赋值运算符),它会将指针复制到引用计数器,因此彼此创建的所有shared_ptr个实例都会维护一个计数器,并保证正确删除。但是如果你有两个不相关的shared_ptr对象的“族”到同一个对象(其中两个或多个指针是从同一个原始指针创建的),那些“家族”彼此不了解,并将单独引用,当它达到0时,每个都会被删除。

这在实践中意味着,在使用std::tr1::shared_ptr时,必须遵守某些规则。这取决于您使用的实现。

使用shared_ptr<T> x(new T(...)); 或更早版本的Boost版本,唯一完全安全的对象分配模式是:

new

换句话说,shared_ptr的结果应该立即放入auto_ptr<T> x(new T); ... shared_ptr<T> y(x); - 然后您可以根据需要复制后者。

一个相当安全的模式也是这样:

shared_ptr

auto_ptr在从auto_ptr初始化时实现通常的所有权转移,后者的语义(只要它们被正确遵循)确保只有一个shared_ptr应该存在一个对象;因此,从中构建auto_ptr是安全的。

有时您还必须处理不使用shared_ptr表示指针所有权转移的C ++库,而只是记录特定函数的意图。在这些情况下,使用std::shared_ptr也应该是安全的,但当然您应该确保您已正确理解文档...

在C ++ 0x boost::shared_ptr中,在较新版本的shared_ptr<int> p = make_shared<int>(123); 中,提供了helper以确保正确实例化共享对象:

make_shared<T>()

shared_ptr<T>的返回类型已经是{{1}},因此您不会在代码中处理原始指针,从而减少出错的可能性。

答案 1 :(得分:2)

第一步是使用-g 3开关编译程序,以便获得更多调试信息。

没有太多可以继续的,但是当向量重新分配增长时,有可能会调用~MSG()

以下是您代码的最简单版本:

#include <vector>

struct MSG {
  MSG(int count);
  ~MSG();
  void destroy();
  char* payload;
  int payloadLen;
};

MSG::MSG(int count): payload(new char[count]), payloadLen(count) {}

MSG::~MSG() {
    destroy();
}

void MSG::destroy() {
    if (payload != NULL)
        delete[] payload;
    payload = NULL;
    payloadLen = 0;
}

std::vector<MSG> queueto1;

int main() {
    queueto1.push_back(MSG(10));
    return 0;
}

And G++ says

  

块释放两次

     

退出:ExitFailure 127

现在,当调用push_back(MSG(10))时,它正在创建一个临时MSG实例,然后使用默认复制构造函数在向量中设置项目临时的~MSG()(删除向量中的项目现在指向的有效负载)。

您可以将复制构造函数MSG(const MSG& copy)实现为传输所有权,但这需要在副本上使用const_cast并且非常混乱。

有两个简单的选项MSG::payload是共享指针,或者queueto1是std::vector<MSG*>

答案 2 :(得分:1)

跟踪内存分配和损坏问题,您可以使用valgrindRational Purify

答案 3 :(得分:0)

打印“payLoad”指针和计数器作为删除发生。然后你可以看到一个双重自由发生,删除它是...

答案 4 :(得分:0)

根据Valgrind的说法,崩溃发生在CONSTCHP2MSLST实例的破坏中。问题可能在于messageListener实施。你能发布相关的contstructors并复制该类的构造函数吗?

答案 5 :(得分:0)

副作用:你说你没有定义析构函数,而是使用了STL容器和共享指针。容器和共享指针为您做的是为您调用析构函数。它们不会取代析构函数;它们取代了何时使用析构函数的需要。

这意味着你的类得到了默认的析构函数,它包含调用基类和成员上的任何析构函数。如果您的类实际上没有任何资源,除非通过智能指针等,并且您没有使用多态,这可以工作。它确实比“我正在使用STL构造和智能指针更需要关注,所以我不需要析构函数”。

如果您有class Aclass B: public A,并且您指向A的指针指向B(原始指针,智能指针,则无关紧要) ),你需要一个虚拟析构函数。否则,当删除指向A的指针时,它将销毁A的所有子类和成员,并忽略B中添加的任何内容。因此,您应该在将以这种方式使用的任何基类中具有虚拟析构函数,并且出于安全考虑,您应该在具有任何虚函数的任何基类中具有虚拟析构函数。即使您没有在析构函数中删除任何内容,也应该在virtual A::~A() {}的定义中使用A

答案 6 :(得分:0)

关于上面的析构函数的观点是正确的,但请注意,如果你没有定义析构函数,你总是会在类中得到一个空的非虚析构函数。如果你的析构函数必须是虚拟的,那么至少第一个基类析构函数必须是虚拟的 - 当然所有派生类都将自动为虚拟,但为了便于阅读,仍然应该使用虚拟关键字。

在具有智能指针的双重免费情况下要查找的一个常见错误是确保您的共享指针实例永远不会通过引用传递 - 或者如果它们在单线程实现中通过引用传递,则实例立即生效按值复制 - 与某些STL容器中的共享指针一样。

如果通过引用传递或者提取并释放了原始指针,则很可能实现双重释放。

最后一个问题是,如果实现是多线程的,那么共享指针实现必须具有引用计数的线程安全实现,否则,可能发生双重释放或泄漏。