Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c++ destructor mess, impossible to debug

when I run my program everything goes fine. At the end it prints out this:

*** 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]

I checked out, it happens when a function returns, where all the objects of the program are instatied automatically. Anyway I didn't define any destructor for these objects, I tried to use STL Containers and TR1 shared_ptr. I guess everything happens in the default destructors. Is there a way to know where does it breaks up? I mean, I'd like to know which object distruction does the whole mess. I'm using these containers and shared pointers:

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

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

messageListener doesn't have a distructor. And two of these vectors:

std::vector<MSG> queueto1;

where MSG destructor is:

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

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

that never gave problems before and I this it shouldn't ever...

Any recomendations how to track this problem? I'm clueless...

EDIT:

HERE IS VALGRIND OUTPUT:

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)
like image 639
gotch4 Avatar asked Dec 04 '09 17:12

gotch4


2 Answers

Judging by your Valgrind output, the problem is that an object pointed to by a shared_ptr is being deleted twice. One possibility of getting that is if you initialize two shared_ptr with the same raw pointer, e.g.:

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

shared_ptr is not magical, and it cannot know if the object you ask it to take care of is already owned by some other unrelated shared_ptr, if all you give it is a raw pointer. In the example above, each shared_ptr will create its own reference counter, initialized to 1; when they die, each will decrement its own counter, see that it is 0, and delete the object, producing double deletion. I suspect your case is similar. If you show the code used to initialize shared_ptr objects that are added to the vector, this can be verified.

[EDIT] Since this is validated to be the cause of the crash now, let me elaborate a bit on how to properly use shared_ptr.

First of all, the nature of the problem. The way shared_ptr is written, it works with any C++ type, and provides reference counting semantics. The obvious problem is that most types do not provide any space to store the reference counter (e.g. consider shared_ptr<int> - there's no extra space "inside" int). To work around this, for every shared object, a separate block of memory is allocated that contains the reference counter. This is done whenever you create a shared_ptr from a raw pointer. The shared_ptr object itself then stores the original raw pointer, and the pointer to reference counter (which is why it's more "fat" than a raw pointer, which can be trivially checked with sizeof). When you create one shared_ptr from another (using copy constructor or assignment operator), it copies the pointer to reference counter, so all shared_ptr instances created from one another maintain a single counter, and guarantee correct deletion. But if you have two unrelated "families" of shared_ptr objects to the same objects (where two or more pointers were created from the same raw pointer), those "families" do not know about each other, and will refcount separately, and each will delete when it hits 0.

What this means in practice is that, when using shared_ptr, one must adhere to certain rules. Those depend on which implementation you use.

With std::tr1::shared_ptr, or older Boost versions, the only fully safe pattern for object allocation is this:

shared_ptr<T> x(new T(...));

In other words, the result of new should be immediately put into a shared_ptr - you can then copy the latter as much as you want.

A reasonably safe pattern is also this:

auto_ptr<T> x(new T);
...
shared_ptr<T> y(x);

shared_ptr implements the usual transfer-of-ownership when initializing from auto_ptr, and the semantics of the latter (so long as they're correctly followed) ensure that only one auto_ptr to an object should exist; thus, it is safe to construct a shared_ptr from that.

Sometimes you also have to deal with C++ libraries which do not use auto_ptr to indicate transfer of pointer ownership, but simply document the intent for specific functions. In those cases it should also be safe to use shared_ptr, but of course you should be sure that you've understood the documentation correctly...

In C++0x std::shared_ptr, and in newer versions of boost::shared_ptr, there's a helper provided to ensure correct instantiation of shared objects:

shared_ptr<int> p = make_shared<int>(123);

The return type of make_shared<T>() is already shared_ptr<T>, so at no point you're dealing with raw pointers in your code, reducing chance to get something wrong.

like image 84
Pavel Minaev Avatar answered Oct 10 '22 02:10

Pavel Minaev


First step is to compile your program with -g 3 switch so that you get much more debugging info.

There's not a lot to go on, but there is a possibility that your ~MSG() is getting called when the vector reallocates to grow.

Here's the most simple version of your code:

#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:

block freed twice

Exited: ExitFailure 127

Now when the push_back(MSG(10)) is being called, it is creating a temporary MSG instance and then using the default copy constructor to set the item in the vector, before calling ~MSG() on the temporary (which deletes the payload that the item in the vector now points at).

You can implement the copy constructor MSG(const MSG& copy) to transfer ownership, but that requires a const_cast on the copy and is very very messy.

Two easy options are available: MSG::payload is a shared pointer, or queueto1 is a std::vector<MSG*>

like image 35
Will Avatar answered Oct 10 '22 01:10

Will