开发者

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::messageList开发者_开发技巧ener> >, 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)


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.


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


to trace memory allocation and corruption problems you can use valgrind or Rational Purify


Printf the "payLoad" pointer and a counter as the deletes happen. You can then see a double free happening and which deletes it is ...


According to Valgrind the crash is taking place in the destruction of your CONSTCHP2MSLST instance. The problem likely resides in your messageListener implementation. Could you post the relevant contstructors and copy constructors for that class?


A side issue: you said you didn't define destructors, but rather used STL containers and shared pointers. What the containers and shared pointers do for you is call your destructors for you. They don't replace the destructor; they replace the need to figure out when to use the destructor.

This means that your classes got the default destructor, which consists of calling whatever destructors there are on base classes and members. If your classes don't actually own any resources except through smart pointers and the like, AND you're not using polymorphism, this can work. It does need more care than "I'm using STL constructs and smart pointers, so I don't need destructors".

If you have class A and class B: public A, and you ever have a pointer to A pointing to a B (raw pointer, smart pointer, it doesn't matter), you need a virtual destructor. Otherwise, when the pointer to A is deleted, it will destroy all subclasses and members of A, and ignore anything added in B. Therefore, you should have a virtual destructor in any base class that will be used in this way, and for safety's sake you should have a virtual destructor in any base class with any virtual function. Even if you don't have anything to get rid of in the destructor, you should have virtual A::~A() {} in your definition of A.


The point about destructors above is correct, although note, if you don't define a destructor, you always get an empty non-virtual destructor in a class. If your destructor has to be virtual, at least the first base class destructor has to be virtual - and of course all derived classes will automatically be virtual, but should still use the virtual keyword just for readability.

One common error to look for in double-free cases with smart pointers is to make sure that your shared pointer instances are never passed by reference - or if they are passed by reference in a single-threaded implementation, the instance is immediately copied by value - as happens with shared pointers in some STL containers.

If either passed by reference or the raw pointer is extracted and freed, double frees are quite possible.

One final issue is if the implementation is multithreaded, then the shared pointer implementation has to have a threadsafe implementation for the reference count, otherwise, either a double free or a leak can occur.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜