Introduction
In earlier blog, I discussed a few causes of Mem Leaks:
- forgetting to call delete pointer.
- allocate memory by new, and then reassigning pointer to a different memory block, fail to deallocate the first memory block
- allocate a new memory block, reassigning pointer to the new block in a function and forget to deallocate new memory block inside the function or outside the function.
- fail to deallocate resource in wrapper class's destructor
- fail to use virtual destructur
- lapse listener problem: fail to deregister observer when observer goes offline, thus memory allocated to store the pointer to the Subscriber is not deallocated.
In this blog, I add a few scenarios, some of which involves the use of Smart pointer.
Mem Leak due to Circular Reference
The following program makes use of smart pointer. One object is linked to another.
MyClass
has member pointer.- an instance of
MyClass cl1
is linked to another instance ofMyClass cl2
. And there is the opposite link. - For shared_ptr, each time there is another ownership of the object that the pointer points to, a count is incremented. The count is show by
use_count()
. - Thus when cl1 is linked to cl2:
cl2->next = cl1;
cl1.use_count()
returns 2, because the object thecl1
points at now has 2 ownership fromcl1
, andcl2
.- when
cl2
is deleted, the ownership is decremented to 1. - when the program exists,
cl1
is deleted, the ownership is decremented to 0. There is no leak.
#include <iostream>
#include <memory>
using namespace std;
class MyClass {
public:
MyClass(int node) : node_(node){
}
~MyClass(){
}
shared_ptr<MyClass> next;
int node_;
};
void funct(shared_ptr<MyClass> & cl1){
shared_ptr<MyClass> cl2 = make_shared<MyClass>(2);
cout<<"**init cl2**"<<endl;
cout<<"address of cl2: "<<cl2.get()<<endl;
cout<<"count reference of cl2: "<<cl2.use_count()<<endl;
cout<<"**link cl2 to cl1**"<<endl;
cl2->next = cl1;
cout<<"address of cl1: "<<cl1.get()<<endl;
cout<<"count reference of cl1: "<<cl1.use_count()<<endl;
cout<<"address of cl2: "<<cl2.get()<<endl;
cout<<"count reference of cl2: "<<cl2.use_count()<<endl;
// cl2 is deleted after exit of this function
}
int main(){
cout<<"size of MyClass "<<sizeof(MyClass)<<endl;
cout<<"**init cl1**"<<endl;
shared_ptr<MyClass> cl1 = make_shared<MyClass>(1);
cout<<"address of cl1: "<<cl1.get()<<endl;
cout<<"count reference of cl1: "<<cl1.use_count()<<endl;
funct(cl1);
cout<<"***after cl2 is deleted***"<<endl;
cout<<"address of cl1: "<<cl1.get()<<endl;
cout<<"count reference of cl1: "<<cl1.use_count()<<endl;
}
Output Log:
Size of MyClass 24
**init cl1**
address of cl1: 0x504000000060
count reference of cl1: 1
**init cl2**
address of cl2: 0x5040000000a0
count reference of cl2: 1
**link cl2 to cl1**
address of cl1: 0x504000000060
count reference of cl1: 2
address of cl2: 0x5040000000a0
count reference of cl2: 1
***after cl2 is deleted***
address of cl1: 0x504000000060
count reference of cl1: 1
However, the objects are linked to each other, creating Circular Reference, the ownership in both objects pointed by cl1 and cl2 are 2.
- after we perform
cl2->next = cl1
andcl1->next = cl2
the ownership in both object pointed by cl1 and cl2 are 2. - when cl2 is popped from stack (because funct returns), the ownership of cl1 is still 2 as it is printed out on
main
, afterfunct(cl2)
#include <iostream>
#include <memory>
using namespace std;
class MyClass {
public:
MyClass(int node) : node_(node){
}
~MyClass(){
}
shared_ptr<MyClass> next;
int node_;
};
void funct(shared_ptr<MyClass> & cl1){
shared_ptr<MyClass> cl2 = make_shared<MyClass>(2);
cout<<"**init cl2**"<<endl;
cout<<"address of cl2: "<<cl2.get()<<endl;
cout<<"count reference of cl2: "<<cl2.use_count()<<endl;
cout<<"**link cl2 to cl1 and reverse**"<<endl;
cl2->next = cl1;
cl1->next = cl2;
cout<<"address of cl1: "<<cl1.get()<<endl;
cout<<"count reference of cl1: "<<cl1.use_count()<<endl;
cout<<"address of cl2: "<<cl2.get()<<endl;
cout<<"count reference of cl2: "<<cl2.use_count()<<endl;
// cl2 is deleted after exit of this function
}
int main(){
cout<<"size of MyClass "<<sizeof(MyClass)<<endl;
cout<<"**init cl1**"<<endl;
shared_ptr<MyClass> cl1 = make_shared<MyClass>(1);
cout<<"address of cl1: "<<cl1.get()<<endl;
cout<<"count reference of cl1: "<<cl1.use_count()<<endl;
funct(cl1);
cout<<"***after cl2 is deleted***"<<endl;
cout<<"address of cl1: "<<cl1.get()<<endl;
cout<<"count reference of cl1: "<<cl1.use_count()<<endl;
}
The result is 2 indirect leaks
- each leak size is 40 bytes, while MyClass size is 24 bytes.
size of MyClass 24
**init cl1**
address of cl1: 0x504000000060
count reference of cl1: 1
**init cl2**
address of cl2: 0x5040000000a0
count reference of cl2: 1
**link cl2 to cl1**
**link cl2 to cl1**
address of cl1: 0x504000000060
count reference of cl1: 2
address of cl2: 0x5040000000a0
count reference of cl2: 2
***after cl2 is deleted***
address of cl1: 0x504000000060
count reference of cl1: 2
Program stderr
=================================================================
==1==ERROR: LeakSanitizer: detected memory leaks
Indirect leak of 40 byte(s) in 1 object(s) allocated from:
#0 0x55a8d7a7c20d in operator new(unsigned long) /root/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:95:3
#1 0x55a8d7a808c4 in std::__new_allocator<std::_Sp_counted_ptr_inplace<MyClass, std::allocator<void>, (__gnu_cxx::_Lock_policy)2>>::allocate(unsigned long, void const*) /opt/compiler-explorer/gcc-13.2.0/lib/gcc/x86_64-linux-gnu/13.2.0/../../../../include/c++/13.2.0/bits/new_allocator.h:147:27
#2 0x55a8d7a80433 in std::allocator_traits<std::allocator<std::_Sp_counted_ptr_inplace<MyClass, std::allocator<void>, (__gnu_cxx::_Lock_policy)2>>>::allocate(std::allocator<std::_Sp_counted_ptr_inplace<MyClass, std::allocator<void>, (__gnu_cxx::_Lock_policy)2>>&, unsigned long) /opt/compiler-explorer/gcc-13.2.0/lib/gcc/x86_64-linux-gnu/13.2.0/../../../../include/c++/13.2.0/bits/alloc_traits.h:482:20
#3 0x55a8d7a80433 in std::__allocated_ptr<std::allocator<std::_Sp_counted_ptr_inplace<MyClass, std::allocator<void>, (__gnu_cxx::_Lock_policy)2>>> std::__allocate_guarded<std::allocator<std::_Sp_counted_ptr_inplace<MyClass, std::allocator<void>, (__gnu_cxx::_Lock_policy)2>>>(std::allocator<std::_Sp_counted_ptr_inplace<MyClass, std::allocator<void>, (__gnu_cxx::_Lock_policy)2>>&) /opt/compiler-explorer/gcc-13.2.0/lib/gcc/x86_64-linux-gnu/13.2.0/../../../../include/c++/13.2.0/bits/allocated_ptr.h:98:21
#4 0x55a8d7a80177 in std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count<MyClass, std::allocator<void>, int>(MyClass*&, std::_Sp_alloc_shared_tag<std::allocator<void>>, int&&) /opt/compiler-explorer/gcc-13.2.0/lib/gcc/x86_64-linux-gnu/13.2.0/../../../../include/c++/13.2.0/bits/shared_ptr_base.h:969:19
#5 0x55a8d7a7ff25 in std::__shared_ptr<MyClass, (__gnu_cxx::_Lock_policy)2>::__shared_ptr<std::allocator<void>, int>(std::_Sp_alloc_shared_tag<std::allocator<void>>, int&&) /opt/compiler-explorer/gcc-13.2.0/lib/gcc/x86_64-linux-gnu/13.2.0/../../../../include/c++/13.2.0/bits/shared_ptr_base.h:1712:14
#6 0x55a8d7a7fd15 in std::shared_ptr<MyClass>::shared_ptr<std::allocator<void>, int>(std::_Sp_alloc_shared_tag<std::allocator<void>>, int&&) /opt/compiler-explorer/gcc-13.2.0/lib/gcc/x86_64-linux-gnu/13.2.0/../../../../include/c++/13.2.0/bits/shared_ptr.h:464:4
#7 0x55a8d7a7f012 in std::shared_ptr<std::enable_if<!is_array<MyClass>::value, MyClass>::type> std::make_shared<MyClass, int>(int&&) /opt/compiler-explorer/gcc-13.2.0/lib/gcc/x86_64-linux-gnu/13.2.0/../../../../include/c++/13.2.0/bits/shared_ptr.h:1009:14
#8 0x55a8d7a7e64f in funct(std::shared_ptr<MyClass>&) /app/example.cpp:16:31
#9 0x55a8d7a7ecbc in main /app/example.cpp:39:5
#10 0x7fd109739082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)
Indirect leak of 40 byte(s) in 1 object(s) allocated from:
#0 0x55a8d7a7c20d in operator new(unsigned long) /root/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:95:3
#1 0x55a8d7a808c4 in std::__new_allocator<std::_Sp_counted_ptr_inplace<MyClass, std::allocator<void>, (__gnu_cxx::_Lock_policy)2>>::allocate(unsigned long, void const*) /opt/compiler-explorer/gcc-13.2.0/lib/gcc/x86_64-linux-gnu/13.2.0/../../../../include/c++/13.2.0/bits/new_allocator.h:147:27
#2 0x55a8d7a80433 in std::allocator_traits<std::allocator<std::_Sp_counted_ptr_inplace<MyClass, std::allocator<void>, (__gnu_cxx::_Lock_policy)2>>>::allocate(std::allocator<std::_Sp_counted_ptr_inplace<MyClass, std::allocator<void>, (__gnu_cxx::_Lock_policy)2>>&, unsigned long) /opt/compiler-explorer/gcc-13.2.0/lib/gcc/x86_64-linux-gnu/13.2.0/../../../../include/c++/13.2.0/bits/alloc_traits.h:482:20
#3 0x55a8d7a80433 in std::__allocated_ptr<std::allocator<std::_Sp_counted_ptr_inplace<MyClass, std::allocator<void>, (__gnu_cxx::_Lock_policy)2>>> std::__allocate_guarded<std::allocator<std::_Sp_counted_ptr_inplace<MyClass, std::allocator<void>, (__gnu_cxx::_Lock_policy)2>>>(std::allocator<std::_Sp_counted_ptr_inplace<MyClass, std::allocator<void>, (__gnu_cxx::_Lock_policy)2>>&) /opt/compiler-explorer/gcc-13.2.0/lib/gcc/x86_64-linux-gnu/13.2.0/../../../../include/c++/13.2.0/bits/allocated_ptr.h:98:21
#4 0x55a8d7a80177 in std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count<MyClass, std::allocator<void>, int>(MyClass*&, std::_Sp_alloc_shared_tag<std::allocator<void>>, int&&) /opt/compiler-explorer/gcc-13.2.0/lib/gcc/x86_64-linux-gnu/13.2.0/../../../../include/c++/13.2.0/bits/shared_ptr_base.h:969:19
#5 0x55a8d7a7ff25 in std::__shared_ptr<MyClass, (__gnu_cxx::_Lock_policy)2>::__shared_ptr<std::allocator<void>, int>(std::_Sp_alloc_shared_tag<std::allocator<void>>, int&&) /opt/compiler-explorer/gcc-13.2.0/lib/gcc/x86_64-linux-gnu/13.2.0/../../../../include/c++/13.2.0/bits/shared_ptr_base.h:1712:14
#6 0x55a8d7a7fd15 in std::shared_ptr<MyClass>::shared_ptr<std::allocator<void>, int>(std::_Sp_alloc_shared_tag<std::allocator<void>>, int&&) /opt/compiler-explorer/gcc-13.2.0/lib/gcc/x86_64-linux-gnu/13.2.0/../../../../include/c++/13.2.0/bits/shared_ptr.h:464:4
#7 0x55a8d7a7f012 in std::shared_ptr<std::enable_if<!is_array<MyClass>::value, MyClass>::type> std::make_shared<MyClass, int>(int&&) /opt/compiler-explorer/gcc-13.2.0/lib/gcc/x86_64-linux-gnu/13.2.0/../../../../include/c++/13.2.0/bits/shared_ptr.h:1009:14
#8 0x55a8d7a7ec0d in main /app/example.cpp:35:31
#9 0x7fd109739082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)
SUMMARY: AddressSanitizer: 80 byte(s) leaked in 2 allocation(s).