Debugging and Fixing Mem Leaks – Part 2

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 of MyClass 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 the cl1 points at now has 2 ownership from cl1, and cl2.
    • 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 and cl1->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, after funct(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).

Leave a Reply

Your email address will not be published. Required fields are marked *