Search code examples
c++pointersmemory-leaksvalgrind

valgrind leak error after deletion of all dynamic memory


I have tried to learn CPP pointer as well as to free all the memory about which I have used valgrind. But unfortunately I am getting leak error and I don't know where I am making the mistake. Also not so much idea about finding error as a human-readable way from valgrind. Any guidance to find the leak is highly appreciable.

compiler: g++ (Ubuntu 7.5.0-3ubuntu1~16.04) 7.5.0

related info regarding snippet

  • smart pointer is not used intentionally
  • it is a minimal example code. So, some portion might seem to be unnecessary.

file.cpp

#include <iostream>

void algo_fun(unsigned int*  ip_ptr_array_,
              unsigned int   ip_size_,
              unsigned int** op_ptr_array_,
              unsigned int*  op_size_)
{
    *(op_size_) = ip_size_ + 2;

    // following approach is good as it allocate dynamic memory
    unsigned int* local = new unsigned int[*(op_size_)];

    for (unsigned int i = 0; i< *(op_size_); i++)
    {
        local[i]=i+1*3;
    }

    *op_ptr_array_ = &local[0];
    local[3] = 87;
}

int main()
{
    // input array's contetnt
    unsigned int ip_size = 10;
    unsigned int* ip_ptr_array = new unsigned int[ip_size];

    // output data
    unsigned int op_size;
    unsigned int* op_ptr_array;

    // filling input array
    for(unsigned int i = 0; i < ip_size; i++)
    {
        ip_ptr_array[i] = i+2*2;
    }

    // function calling to get output data
    algo_fun(ip_ptr_array,
             ip_size,
             &op_ptr_array,
             &op_size);

    delete [] ip_ptr_array;
    delete [] op_ptr_array;

    return 0;
}

Working version will be found here.

command used to test: valgrind --leak-check=full --show-leak-kinds=all -v ./file

Leak Summary from valgrind

==23138== LEAK SUMMARY:
==23138==    definitely lost: 0 bytes in 0 blocks
==23138==    indirectly lost: 0 bytes in 0 blocks
==23138==      possibly lost: 0 bytes in 0 blocks
==23138==    still reachable: 72,704 bytes in 1 blocks
==23138==         suppressed: 0 bytes in 0 blocks
==23138== 
==23138== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
==23138== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)


Solution

  • tl;dr Please check that you are using a recent Valgrind.

    With a combination of Valgrind and gdb you should be able to see what is going on.

    I get the following (FreeBSD 12.2, g++ 10.3.0, Valgrind built from git HEAD, [OS and compiler version are not relevant]). I'm using the --trace-malloc=yes option to see all the mallloc/free calls. Don't do that on a large application.

    $ valgrind --leak-check=full --trace-malloc=yes ./test
    
    --61886-- malloc(72704) = 0x5800040
    --61886-- calloc(1984,1) = 0x5811C80
    --61886-- calloc(104,1) = 0x5812480
    --61886-- calloc(224,1) = 0x5812530
    --61886-- calloc(80,1) = 0x5812650
    --61886-- calloc(520,1) = 0x58126E0
    --61886-- calloc(88,1) = 0x5812930
    --61886-- _Znam(40) = 0x58129D0
    --61886-- _Znam(48) = 0x5812A40
    --61886-- _ZdaPv(0x58129D0)
    --61886-- _ZdaPv(0x5812A40)
    --61886-- free(0x5800040)
    ==61886== 
    ==61886== HEAP SUMMARY:
    ==61886==     in use at exit: 3,000 bytes in 6 blocks
    ==61886==   total heap usage: 9 allocs, 3 frees, 75,792 bytes allocated
    
    

    _Znam is the mangled version of array new and _ZdaPv the mangled version of array delete from your code. The other calls to malloc/calloc/free come from libc/libstc++. You will probably see different traces on Linux or with libc++.

    You could use --show-reachable=yes to get information on the reachable memory.

    If I now run under gdb.

    $ gdb ./test
    (gdb) b malloc
    (gdb) b malloc
    (gdb) r
    Breakpoint 1, malloc (nbytes=96) at /usr/src/libexec/rtld-elf/rtld.c:5877
    

    This is a call to malloc in the link loader, ld.so. I then executed several more 'r's until

    Breakpoint 1, __je_malloc_initialized () at jemalloc_jemalloc.c:208
    

    Here the link loader has loaded libstdc++.so and global malloc has been replaced by jemalloc.

    Getting the callstack

    (gdb) bt
    #0  __je_malloc_initialized () at jemalloc_jemalloc.c:208
    #1  imalloc (sopts=<optimized out>, dopts=<optimized out>) at jemalloc_jemalloc.c:1990
    #2  __malloc (size=72704) at jemalloc_jemalloc.c:2042
    #3  0x00000008006eb6f4 in ?? () from /usr/local/lib/gcc10/libstdc++.so.6
    #4  0x000000080060e2fd in objlist_call_init (list=<optimized out>, lockstate=<optimized out>) at /usr/src/libexec/rtld-elf/rtld.c:2820
    #5  0x000000080060d03d in _rtld (sp=0x7fffffffe408, exit_proc=0x7fffffffe3d0, objp=0x7fffffffe3d8) at /usr/src/libexec/rtld-elf/rtld.c:811
    #6  0x000000080060a8c9 in rtld_start () at /usr/src/libexec/rtld-elf/amd64/rtld_start.S:39
    #7  0x0000000000000000 in ?? ()
    (gdb) 
    
    

    Note the same allocation size on line #2.

    I'm not going to dig into what this memory is for, something part of the C++ runtime.

    libstdc++ (and libc) deliberately do not free this memory (presumably this is either difficult or just not worth it). In order to filter out these allocations, Valgrind uses a special function to ask libstc++/libc to free this memory.

    The option in this case is

        --run-cxx-freeres=no|yes  free up libstdc++ memory at exit on Linux
    

    (strictly speaking, not just Linux).

    Looking at the Valgrind release notes

    Release 3.12.0 (20 October 2016)
    ...
    * New option --run-cxx-freeres=<yes|no> can be used to change whether
      __gnu_cxx::__freeres() cleanup function is called or not. Default is
      'yes'.
    
    

    So I presume that you are using version 3.11 or earlier.

    Finally, if I use clang++/libc++ there is no runtime allocation (and also no freeres function).