Debugging and Fixing Mem Leaks – Part 1

Introduction

In the earlier blog, I explained the basic of Address Sanitizer tool and several errors and its messages that the tool can detect.

In this blog, I will dive deeper into Memory Leak issue. I will make several examples to detail common programming mistakes that result in Memory Leak. While there is only 1 simple cause for Memory Leak: the programmer forgets to deallocate (free up) memory block after it was allocated (by use of new/malloc/memset/callocreallocvalloc or memalign); the mistakes or the scenarios that cause mem leaks, however, are numerous.

This blog summarizes some common causes of direct leaks, and indirect leaks such as:

  • failure to create a separate event to handle deallocating memory
  • mem leak due to reassigning an argument pointer inside a function and failing to deallocate the original heap mem when the function exits.
  • failure to create virtual destructor.
  • forget to deregister an observer in Observer pattern design. (also called lapse listener problem)

Direct Leak

Mem leak because of missing event to deallocate mem

I have the following program that utilizes pass by pointer to change data at the memory block pointed by a pointer msg.

  • Pointer msg points to data block which stores MyMessage as data type.
  • The pointer is passed into function handleMyMessage as a parameter.
  • handleMyMessage creates another pointer, and assigns it to the input pointer msg.
  • handleMyMessage populates that data block pointed by msg using its internal logic.
  • because pass by pointer is used, the data at the heap location pointed by msg is also changed, and is accessible outside handleMyMessage.
  • However, there is nowhere in the codes that frees up the memory block pointed by msg.
#include <iostream>
#include <string>
using namespace std;

# define HELLO 1

typedef struct MyMessage {
    int msgID;
    string msgContend;
} MyMessage;

void handleMyMessage (MyMessage * msg, int EVENT){

    MyMessage * newMsg = msg;
    switch (EVENT)
    {
        case HELLO: 
            newMsg->msgID=5;
            newMsg->msgContend="Hello!";
            break;
        default:
            break;
    }
    msg = newMsg;

}

int main(){
    cout<<"size of MyMessage "<<sizeof(MyMessage)<<endl;
    MyMessage *newMsg = new MyMessage;   
    handleMyMessage (newMsg, HELLO);  
    // output: 
    // MessageID=5 Contend= Hello!
    cout<<"MessageID="<<newMsg->msgID<<" Contend= "<<newMsg->msgContend<<endl;
    return 0;
}

The above program would have no run time error; however, when compiling with flag -fsanitizer=address, we will have mem leak error. This is quite clear: we didn't clear newMsg.

  • The error log indicates that we didn't free up the memory block pointed by newMsg pointer, created at line 30
  • the size of the direct leak is 30 bytes, same size of MyMessage.

Executor x86-64 clang 17.0.1 (C++, Editor #1)

x86-64 clang 17.0.1
x86-64 clang 17.0.1
-fsanitize=address
Program returned: 1
MessageID=5 Contend= Hello!
Program stderr

=================================================================
==1==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 40 byte(s) in 1 object(s) allocated from:
    #0 0x5578523eb1cd in operator new(unsigned long) /root/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:95:3
    #1 0x5578523ed561 in main /app/example.cpp:30:25
    #2 0x7fa523c1e082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)

SUMMARY: AddressSanitizer: 40 byte(s) leaked in 1 allocation(s).

We can fix the issue by simply deleting newMsg in main. However, I present 2 other methods that I see commonly used in the industry.

Fixing with delete in a separate event

We could fix mem leak by adding another event to delete msg.

  • We can delete the memory block from inside handleMyMessage.
  • or we can delete the memory block in main or using another function that deletes the memory block. The function could be invoked by another event.

In the following program, I created another function that could be invoked in another time, identified by event=RECEIVED_HELLO

#include <iostream>
#include <string>
using namespace std;

# define HELLO 1
# define RECEIVED_HELLO 1

typedef struct MyMessage {
    int msgID;
    string msgContend;
} MyMessage;

void handleMyMessage (MyMessage * msg, int EVENT){

    MyMessage * newMsg = msg;
    switch (EVENT)
    {
        case HELLO: 
            newMsg->msgID=5;
            newMsg->msgContend="Hello!";
            break;
        default:
            break;
    }
    msg = newMsg;

}

void clearMyMessage (MyMessage * msg, int EVENT){

    MyMessage * newMsg = msg;
    switch (EVENT)
    {
        case RECEIVED_HELLO: 
            delete newMsg;
            break;
        default:
            break;
    }

}

int main(){

    MyMessage *newMsg = new MyMessage;   
    handleMyMessage (newMsg, HELLO);   
    cout<<"MessageID="<<newMsg->msgID<<" Contend= "<<newMsg->msgContend<<endl;
    clearMyMessage( newMsg, RECEIVED_HELLO );

}

Note: If we delete the memory block from within handleMyMessage, note that we cannot print out msgID and msgContend after existing handleMyMessage. We would get heap-use-after-free error. Thus creating an event and invoking a separate function to clear up the message when we no longer need that message is better.

Fixing with smart pointer

We could fix mem leak by using shared_pointer.

Note that to use shared_pointer, we must #include<memory>

#include <iostream>
#include <string>
#include <memory>
using namespace std;

# define HELLO 1

typedef struct MyMessage {
    int msgID;
    string msgContend;
} MyMessage;

void handleMyMessage (std::shared_ptr<MyMessage> msg, int EVENT){

    std::shared_ptr<MyMessage> newMsg = msg;
    switch (EVENT)
    {
        case HELLO: 
            newMsg->msgID=5;
            newMsg->msgContend="Hello!";
            break;
        default:
            break;
    }
    msg = newMsg;

}

int main(){

    std::shared_ptr<MyMessage> newMsg = std::make_shared<MyMessage>();   
    handleMyMessage (newMsg, HELLO);   
    cout<<"MessageID="<<newMsg->msgID<<" Contend= "<<newMsg->msgContend<<endl;

}

Mem leak by reassigning pointer

Reassigning pointer could easily cause mem leak if not done properly.

The following program illustrates such scenario

  • MyClass pointer, math, is created, and heap memory is allocated at where the pointer points to.
  • The pointer is then reassigned, it is pointed to another location, which is allocated. (Yeah, I changed class a lot). So we have 2 memory locations now.
  • MyClass itself has a private pointer, resource_, type Resource. Thus, for each memory location that was created, there is another section of memory that corresponds to Resource and is accessible by the private pointer resource_;
  • When MyClass pointer, math, is deleted, only the later memory location is deleted.
#include <iostream>
#include <string>
using namespace std;

class MyClass {
    public:
    MyClass ( string teacher) 
    : teacher_(teacher)
    {}
    private:
    string teacher_;
};

int main(){
    MyClass * math = new MyClass("Tonny Blair" ); // memory blk isn't deallocated
    math = new MyClass ( "George Bush"); // only this memory blk is deallocated
    delete math;
}

Error log:

=================================================================
==1==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 32 byte(s) in 1 object(s) allocated from:
    #0 0x5600c987325d in operator new(unsigned long) /root/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:95:3
    #1 0x5600c987565a in main /app/example.cpp:15:22
    #2 0x7f369ccdd082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)

SUMMARY: AddressSanitizer: 32 byte(s) leaked in 1 allocation(s).

Mem Leak in pass by pointer usage

From the first example, we see that failure of deallocate memory causes mem leak. But why do sometimes we still have mem leak while already deallocating memory?

The 2 earlier example simplifies the mistakes; one would say they are obvious. What kind of programmer would make such mistakes? In this section, I make another example, the mistake here is a bit more subtle.

Mem leak would happen if we were not aware of the memory "manipulation"; in order words, we accidently allocate more memory and are not aware of. It is the case illustrated in the second example.

The following program is similar to the first example but with some changes. Now we properly deallocate the memory at the end of the program. However, when passing pointer to the function, we also pass the reference of the pointer, and accidently allocate more memory, and reassign the pointer to point to that newly allocated memory location.

  • passing by reference cause the changes to the parameter inside the function will also changes the item in the calling function.
  • if a pointer is passed by reference to a function, and a pointer is reassigned, then in the calling function, the pointer is also changed.
  • consequently, the memory that was originally pointed to is now "orphaned"; it cannot be reached by anything.
  • despite that we already do delete ptr in main, we still have mem leak.
#include <iostream>
using namespace std;

void funct (int *&ptr){
    ptr = new int(600);
}

int main(){
    int *ptr = new int(100);
    funct(ptr);
    delete ptr;
}

Error log:

  • the error log indicates the leak of the memory allocated for the object in line 9, in the original declaration and allocation of ptr.
================================================================
==1==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 4 byte(s) in 1 object(s) allocated from:
    #0 0x555e9aab516d in operator new(unsigned long) /root/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:95:3
    #1 0x555e9aab75b0 in main /app/example.cpp:9:16
    #2 0x7f67453be082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)

SUMMARY: AddressSanitizer: 4 byte(s) leaked in 1 allocation(s).

Can we not use pointer reference? In fact, reference or no reference, there is still leak.

The following program uses no pointer reference:

#include <iostream>
using namespace std;

void funct (int *ptr){
    ptr = new int(600);
}

int main(){
    int *ptr = new int(100);
    funct(ptr);
    delete ptr;
}

Error log:

  • now it shows that the troubling line is 5. Indeed, it is at the point when a new memory block is allocated, but not deleted.
  • Contrasting to earlier example, the original memory block is deallocated because no pointer reference is passed into funct.
================================================================
==1==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 4 byte(s) in 1 object(s) allocated from:
    #0 0x56352aa7816d in operator new(unsigned long) /root/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:95:3
    #1 0x56352aa7a465 in funct(int*) /app/example.cpp:5:11
    #2 0x56352aa7a528 in main /app/example.cpp:10:5
    #3 0x7f4483853082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)

SUMMARY: AddressSanitizer: 4 byte(s) leaked in 1 allocation(s).

Fix with const keyword:

I personally think that const parameter should strictly be used when a pointer is passed as parameter to a function. The keyword will cause compile error if we accidently reassign the pointer to point to a different heap memory. As show in the preceding section, improper reassigning pointer causes mem leak.

#include <iostream>
using namespace std;

void funct (int * const ptr){      // const parameter
    // ptr = new int(600);         // compile error if run this line
    *ptr = 8;
}

int main(){
    int * ptr = new int(100);     // normal pointer
    cout<<*ptr<<endl;    // print 100
    funct(ptr);
    cout<<*ptr<<endl;    // print 8
    delete ptr;
}

We still achieve our objective but have no mem leak with *ptr = 8 , that is we don't allocate new heap mem with new keyword. Const keyword prevent that.

Note the syntax of const pointer is <value_type> * const <variable_name>

Error if line 5 is uncommented:

<source>:5:9: error: cannot assign to variable 'ptr' with const-qualified type 'int *const'
    5 |     ptr = new int(600);
      |     ~~~ ^
<source>:4:25: note: variable 'ptr' declared const here
    4 | void funct (int * const ptr){
      |             ~~~~~~~~~~~~^~~
1 error generated.

Mem leak by missing virtual destructor

We could have BaseClass and a DerivedClass, and a memory to Derived class is allocated, we expect that:

  • BaseClass constructor is called first, then DerivedClass constructor is called second.
  • BaseClass destructor is called first, then DerivedClass destructor is called second.

If we create a pointer of type DerivedClass, and point it to Derived object, there never be any problem:

#include <iostream>

using namespace std;

class BaseClass {
    public:
    BaseClass(){ cout<<"BaseClass constructor"<<endl; }
     ~BaseClass(){ cout<<"BaseClass destructor"<<endl; }
};

class DerivedClass : public BaseClass {
    public:
    DerivedClass(){ cout<<"DerivedClass constructor"<<endl; }
     ~DerivedClass(){ cout<<"DerivedClass destructor"<<endl; }
};

int main(){
    DerivedClass * cl = new DerivedClass();
    delete cl;
}

Output:

BaseClass constructor
DerivedClass constructor
DerivedClass destructor
BaseClass destructor

However, if we have a pointer of type BaseClass and point it to DerivedClass object, and if we don't have virtual keyword on the destructor, we will have mem leak. The reason is that the destructor of DerivedClass is never called.

#include <iostream>
using namespace std;

class BaseClass {
    public:
    BaseClass(){ cout<<"BaseClass constructor"<<endl; }
     ~BaseClass(){ cout<<"BaseClass destructor"<<endl; }
};

class DerivedClass : public BaseClass {
    public:
    DerivedClass(){ cout<<"DerivedClass constructor"<<endl; }
     ~DerivedClass(){ cout<<"DerivedClass destructor"<<endl; }
};

int main(){
    BaseClass * cl = new DerivedClass();  // problem here
    delete cl;
}

Output:

  • the Derived destructor is not called, causing possible leak.
BaseClass constructor
DerivedClass constructor
BaseClass destructor

As per C++11 standard §5.3.5/3:

If the static type of the object to be deleted is different from its dynamic type, the static type shall be a base class of the dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined.

In other words, in some cases of inheritance, if you don’t have destructors the behavior is undefined.

It means that anything can happen. It might crash or leak memory or it may appear to work as expected.

The following program provides a more obvious leak. The Child class has a member value with is deallocated when the destructor is called. However, without virtual keyword, the Child class destructor is never called, and the Child's member val is never properly deallocated.

#include <iostream>
using namespace std;

class Parent{
    public:
    Parent(){};
    ~ Parent(){ cout<<"Parent destructor"<<endl;} // need virtual to prevend leak
    void setVal ( int * x) {};
};

class Child : public Parent {
    public:
    Child(){}
    ~ Child( ){ 
        cout<<"Child destructor"<<endl;
        delete val;      // must deallocating member
        val = nullptr;
    }
    void setMsg ( int * x) {
        val  = x;
    }
    int *val = nullptr;  // need nullptr to prevend seg fault in line 16
};

int main(){
   Parent *myParent = new Child();
   int * a = new int(5);
   myParent -> setVal (a);
   delete myParent; 
}

Indirect leak

Direct leak vs Indirect leak

According Address Sanitizer githup document:

  • direct leak: memory location not reachable from anywhere.
  • indirect leak: memory location reachable from other leaked blocks.

Indirect Leak in Lapsed listener problem

Observer Pattern is the design pattern that would include a Subscriber and Publisher entities.

  • Subscribers listen to messages from Publisher.
  • The Publishers
    • broadcasts messages to its Subscribers.
    • maintains a list of Subscribers, usually a list of pointers.
    • registers and unregisters the Subscribers.

The Publisher maintains a strong reference to the Subscribers: it maintains a data structure containing Subscribers, and it must explicitly register/unregisters the Subscribers.

Lapse Listener problem occurs when the Publisher registers Subscribers but fails to unregister the Subscriber. When a Subscriber is deleted, the memory allocated for the Subscriber is deallocated. However, the "Subscriber" maintained by the Publisher is still there. In particular:

  • when Publisher registers a Subscriber, it adds the Subscriber to a list. Thus it must allocate some memory to store the pointer/reference to the Subscriber.
  • when the Subscriber is deleted but not unregistered, the pointer/reference to the Subscriber points to invalid memory location. The pointer/reference should be deleted, and the memory location that stores the pointer/reference should be deallocated.
    • Reading/accessing/ or broadcasting messages from the Publisher will cause either SegFault, Undefined behavior or Heap-Use-After-Free error.
    • Otherwise, as long as the Publisher still runs until the program termination; indirect leak occurs when the Publisher is deleted. It is indirect leak because the memory location that stores the pointer/reference to the Subscriber is not deallocated.

The following program illustrates the scenario when indirect leak occurs:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

class ISubscriber {
    public: 
    virtual void onNotify()=0;
};

class IPublisher {
    public: 
    virtual void registerSubscriber(ISubscriber *)=0;
    virtual void unregisterSubscriber(ISubscriber *)=0;
    virtual void notify()=0;
};

class Subscriber : public ISubscriber {
    public:
    virtual void onNotify() { cout<<"Subcriber received msg"<<endl; }
};

class Publisher : public IPublisher {
    public:
    virtual void registerSubscriber(ISubscriber * subscriber) {
         subscriberList.push_back(subscriber);
    }
    virtual void unregisterSubscriber(ISubscriber * subscriber) {
        auto it = find(subscriberList.begin(), subscriberList.end(), subscriber);
        if ( it != subscriberList.end()){
            subscriberList.erase(it);
        }
    }
    virtual void notify() { 
        cout<<"Publisher sends msg"<<endl;
        for (auto it : subscriberList){
            it->onNotify();
        }
    }
    private:
    vector<ISubscriber *> subscriberList; 
};

int main(){
    
    std::cout<<"size of Subscriber: "<<sizeof(Subscriber)<<endl;
    std::cout<<"size of Publisher "<<sizeof(Publisher)<<endl;
    Subscriber * subscriber = new Subscriber(); 
    Publisher * publisher = new Publisher();
    publisher->registerSubscriber(subscriber);
    publisher->notify();
    delete subscriber;
} // program exits, memory allocated to Publisher is not deallocated.
  // memory allocated to store the pointer to the Subscriber is not deallocated.

Console log:

  • The direct leak occurs because the Subscriber is not deleted before the termination of main().
  • The indirect leak occurs because Publisher didn't unregister the subscriber. Error log points to line 47 and line 26 to hint the issue.
  • sizeof(Subscriber) = 8 bytes and sizeof(Publisher) = 32 bytes.
  • However, the direct leak is 32 bytes = size of Subscriber, and the indirect leak is 8 bytes size of Publisher.
size of Subscriber: 8
size of Publisher 32
Publisher sends msg
Subcriber received msg
Program stderr

=================================================================
==1==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 32 byte(s) in 1 object(s) allocated from:
    #0 0x55a1775761ed in operator new(unsigned long) /root/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:95:3
    #1 0x55a177578574 in main /app/example.cpp:49:29
    #2 0x7f9a9fb5e082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)

Indirect leak of 8 byte(s) in 1 object(s) allocated from:
    #0 0x55a1775761ed in operator new(unsigned long) /root/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:95:3
    #1 0x55a17757a6d7 in std::__new_allocator<ISubscriber*>::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 0x55a17757a0f3 in std::allocator_traits<std::allocator<ISubscriber*>>::allocate(std::allocator<ISubscriber*>&, 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 0x55a17757a0f3 in std::_Vector_base<ISubscriber*, std::allocator<ISubscriber*>>::_M_allocate(unsigned long) /opt/compiler-explorer/gcc-13.2.0/lib/gcc/x86_64-linux-gnu/13.2.0/../../../../include/c++/13.2.0/bits/stl_vector.h:378:20
    #4 0x55a17757970b in void std::vector<ISubscriber*, std::allocator<ISubscriber*>>::_M_realloc_insert<ISubscriber* const&>(__gnu_cxx::__normal_iterator<ISubscriber**, std::vector<ISubscriber*, std::allocator<ISubscriber*>>>, ISubscriber* const&) /opt/compiler-explorer/gcc-13.2.0/lib/gcc/x86_64-linux-gnu/13.2.0/../../../../include/c++/13.2.0/bits/vector.tcc:459:33
    #5 0x55a177579449 in std::vector<ISubscriber*, std::allocator<ISubscriber*>>::push_back(ISubscriber* const&) /opt/compiler-explorer/gcc-13.2.0/lib/gcc/x86_64-linux-gnu/13.2.0/../../../../include/c++/13.2.0/bits/stl_vector.h:1289:4
    #6 0x55a17757890f in Publisher::registerSubscriber(ISubscriber*) /app/example.cpp:26:25
    #7 0x55a1775785f5 in main /app/example.cpp:50:16
    #8 0x7f9a9fb5e082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)

SUMMARY: AddressSanitizer: 40 byte(s) leaked in 2 allocation(s).

If we broadcast message after the subscriber was deleted, by adding notify() after line 53 (delete subscriber), we have the following console log:

  • it prints "Publisher sends message" but not "Subscriber receives message". It is due to undefined behavior.
  • ASan prints Heap-Use-After-Free, because the memory location pointed by the Subscriber pointer in subscriberList is already deallocated..
  • The heap memory that the program tries to access is 8 bytes, corresponding the size of the subscriber.
  • The operation that causes the error is on line 53, the line that we added notify(). The operation is READ.
==1==ERROR: AddressSanitizer: heap-use-after-free on address 0x502000000010 at pc 0x56454cae7040 bp 0x7ffe16cae9f0 sp 0x7ffe16cae9e8
READ of size 8 at 0x502000000010 thread T0
    #0 0x56454cae703f in Publisher::notify() /app/example.cpp:37:17
    #1 0x56454cae66ba in main /app/example.cpp:53:16
    #2 0x7f08a83b0082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)
    #3 0x56454ca0b36d in _start (/app/output.s+0x2c36d)

0x502000000010 is located 0 bytes inside of 8-byte region [0x502000000010,0x502000000018)
freed by thread T0 here:
    #0 0x56454cae4a4d in operator delete(void*) /root/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:152:3
    #1 0x56454cae6665 in main /app/example.cpp:52:5
    #2 0x7f08a83b0082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)

previously allocated by thread T0 here:
    #0 0x56454cae41ed in operator new(unsigned long) /root/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:95:3
    #1 0x56454cae6546 in main /app/example.cpp:48:31
    #2 0x7f08a83b0082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)

SUMMARY: AddressSanitizer: heap-use-after-free /app/example.cpp:37:17 in Publisher::notify()

The curious case of having only indirect leak

The following program results in only indirect leak.

#include <iostream>
using namespace std;

class MyClass {
    public:
    MyClass(int node) : node_(node){ 
        cout<<"MyClass constructor"<<endl;
    }
    ~MyClass(){ 
        cout<<"MyClass destructor" <<endl; 
    }
    MyClass * next;
    int node_; 
};

void myFunction( int x ){
    cout<<"calling myFunction "<<x<<endl;
    if (x>0){
        myFunction(x-1);
    }
    else{
        cout<<"size of MyClass "<<sizeof(MyClass)<<endl;
        MyClass * cl1 = new MyClass(x);     
        MyClass * cl2 = new MyClass(x);
        cl1->next = cl2;
        cl2->next = cl1;
    }
    cout<<"exist myFunction "<<x<<endl;
}

int main(){
    
    myFunction(2);

}

Log indicates that:

  • 2 indirect leaks.
  • each leak has the size of MyClass (16 bytes)
lling myFunction 2
calling myFunction 1
calling myFunction 0
size of MyClass 16
MyClass constructor
MyClass constructor
exist myFunction 0
exist myFunction 1
exist myFunction 2
=================================================================
==1==ERROR: LeakSanitizer: detected memory leaks
Indirect leak of 16 byte(s) in 1 object(s) allocated from:
    #0 0x55a681f931bd in operator new(unsigned long) /root/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:95:3
    #1 0x55a681f95556 in myFunction(int) /app/example.cpp:24:25
    #2 0x55a681f954ec in myFunction(int) /app/example.cpp:19:9
    #3 0x55a681f954ec in myFunction(int) /app/example.cpp:19:9
    #4 0x55a681f9566d in main /app/example.cpp:33:5
    #5 0x7f7e7af33082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)

Indirect leak of 16 byte(s) in 1 object(s) allocated from:
    #0 0x55a681f931bd in operator new(unsigned long) /root/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:95:3
    #1 0x55a681f9552d in myFunction(int) /app/example.cpp:23:25
    #2 0x55a681f954ec in myFunction(int) /app/example.cpp:19:9
    #3 0x55a681f954ec in myFunction(int) /app/example.cpp:19:9
    #4 0x55a681f9566d in main /app/example.cpp:33:5
    #5 0x7f7e7af33082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)

SUMMARY: AddressSanitizer: 32 byte(s) leaked in 2 allocation(s).

Indirect leak of 16 byte(s) in 1 object(s) allocated from:
    #0 0x559bd3a4f1bd in operator new(unsigned long) /root/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:95:3
    #1 0x559bd3a514fb in myFunction(int) /app/example.cpp:22:25
    #2 0x559bd3a514ec in myFunction(int) /app/example.cpp:19:9
    #3 0x559bd3a514ec in myFunction(int) /app/example.cpp:19:9
    #4 0x559bd3a5166d in main /app/example.cpp:33:5
    #5 0x7feed8f4c082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)

SUMMARY: AddressSanitizer: 32 byte(s) leaked in 2 allocation(s).

Reference:

AddressSanitizerLeakSanitizerDesignDocument · google/sanitizers Wiki · GitHub

Observer Pattern and Lapsed Listener Problem (ilkinulas.github.io)

valgrind - What is the difference between a direct and indirect leak? - Stack Overflow

valgrind - Only Indirect leaks and no Direct ones - Stack Overflow

Leave a Reply

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