C ++ destructor mess, impossible to debug

when I run my program, everything is going well. At the end, he prints 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 this happens when the function returns, where all program objects are automatically installed. In any case, I did not define a destructor for these objects, I tried to use the STL and TR1 shared_ptr containers. I think everything happens in default destructors. Is there any way to find out where it breaks up? I mean, I would like to know what destruction of an object makes the whole mess. I use these containers and general pointers:

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

messageListener does not have a distructor. And two of these vectors:

 std::vector<MSG> queueto1; 

where is the MSG destructor:

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

who never gave a problem before, and I never should ...

Any recommendations on how to track this issue? I dont know...

EDIT:

HERE 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) 
+4
source share
7 answers

Judging by the output of Valgrind, the problem is that the object pointed to by shared_ptr is deleted twice. One way to get this is to initialize two shared_ptr using the same raw pointer, for example:

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

shared_ptr not magical, and it cannot know if the object you are asking for belongs to another unrelated shared_ptr , if everything you give it is an unhandled pointer. In the above example, each shared_ptr will create its own reference counter, initialized to 1; when they die, each will decrease its own counter, see that it is 0, and delete the object, performing a double deletion. I suspect your case is similar. If you show the code used to initialize the shared_ptr objects that are added to the vector, this can be checked.

[EDIT] . Since it is confirmed that the cause of the failure is now, let me clarify a bit on how to use shared_ptr correctly.

First of all, the nature of the problem. The shared_ptr method is written, it works with any type of C ++ and provides the semantics of reference counting. The obvious problem is that most types do not provide space for storing a control counter (for example, consider shared_ptr<int> - there is no extra space inside " int ). To get around this, a separate block of memory is allocated for each shared object that contains a control counter. This is done whenever you create shared_ptr from a raw pointer. The shared_ptr object itself saves the original raw pointer and pointer to the reference counter (therefore, it is more "fat" than the original pointer that can be checked it’s trivial with sizeof .) When you create one shared_ptr from another (using the copy constructor or assignment operator), it copies the pointer to the reference counter, so all instances of shared_ptr created from each other support one counter and guarantee correct deletion. But if you have two unrelated "families" of shared_ptr objects for the same objects (where two or more pointers were created from the same source pointer), these "families" do not know about each other and will be recalculated separately, and each will be deleted when it reaches 0.

In practice, this means that when using shared_ptr you must adhere to certain rules. They depend on the implementation you are using.

With std::tr1::shared_ptr or older versions of Boost, the only completely safe template for placing objects is the following:

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

In other words, the result of new should be immediately placed in shared_ptr - you can copy the last as much as you want.

A fairly secure template is also as follows:

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

shared_ptr implements the usual transfer of ownership upon initialization from auto_ptr , and the semantics of the latter (provided that they are respected correctly) guarantee the existence of only one auto_ptr for the object; thus, it is safe to build shared_ptr from it.

Sometimes you also have to deal with C ++ libraries that do not use auto_ptr to indicate the transfer of ownership of a pointer, but simply document the intent of certain functions. In such cases, it should also be safe to use shared_ptr , but, of course, you must be sure that you understand the documentation correctly ...

In C ++ 0x std::shared_ptr , and in newer versions of boost::shared_ptr there is an assistant that provides the correct copying of shared objects:

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

The return type make_shared<T>() already shared_ptr<T> , so by no means do you deal with raw pointers in the code, reducing the chance of getting something wrong.

+27
source

The first step is to compile your program with -g 3 so that you get much more debugging information.

There are not many, but there is a chance that your ~MSG() is called when the vector is redistributed to grow.

Here is the simplest 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 :

the block is freed twice

Output: ExitFailure 127

Now when push_back(MSG(10)) is called, it creates a temporary MSG instance and then uses the default copy constructor to set the element in the vector before calling ~MSG() in the temporary (which removes the payload that the element in the vector now points to )

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

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

+2
source

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

+1
source

Print out the payLoad pointer and counter when deleted. Then you can see the double free event and which removes it ...

0
source

According to Valgrind, a crash occurs when you destroy your CONSTCHP2MSLST instance. The problem is probably related to your implementation of messageListener . Could you place the appropriate constructors and copy constructors for this class?

0
source

Side problem: you said that you did not define destructors, but rather used STL containers and common pointers. What containers and generic pointers do for you is called your destructors for you. They do not replace the destructor; they replace the need to figure out when to use the destructor.

This means that your classes received a default destructor, which consists in calling any destructors on the base classes and members. If your classes actually do not own any resources, except with the help of smart pointers, etc. And you do not use polymorphism, it may work. This requires more caution than "I use STL constructs and smart pointers, so I don't need destructors."

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

0
source

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

One common mistake to look for in cases of dual access with smart pointers is to verify 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 - like this happens with shared pointers in some STL containers.

If either the reference or the raw pointer is retrieved and freed, double releases are possible.

One of the last problems is that the implementation is multi-threading, then the implementation of a common pointer must have a stream loop implementation for counting links, otherwise either double freedom or a leak can occur.

0
source

Source: https://habr.com/ru/post/1203357/


All Articles