Search code examples
cmemory-leaksvalgrind

What does Valgrind mean when it says memory is "definitely lost"?


The following program:

#include <stdlib.h>

int main(void)
{
    char *my_str = malloc(42 * sizeof(char));

    return 0;
}

Compiled as such:

gcc -g -o prog prog.c

And executed with Valgrind as such:

valgrind --leak-check=full ./prog

Produces the following (truncated) output:

...
==18424== HEAP SUMMARY:
==18424==     in use at exit: 42 bytes in 1 blocks
==18424==   total heap usage: 1 allocs, 0 frees, 42 bytes allocated
==18424== 
==18424== 42 bytes in 1 blocks are definitely lost in loss record 1 of 1
==18424==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==18424==    by 0x10865B: main (main.c:5)
==18424== 
==18424== LEAK SUMMARY:
==18424==    definitely lost: 42 bytes in 1 blocks
==18424==    indirectly lost: 0 bytes in 0 blocks
==18424==      possibly lost: 0 bytes in 0 blocks
==18424==    still reachable: 0 bytes in 0 blocks
==18424==         suppressed: 0 bytes in 0 blocks
...

Why does Valgrind say that the unfreed memory is "definitely lost"?

From the Valgrind Memcheck docs:

This means that no pointer to the block can be found. The block is classified as "lost", because the programmer could not possibly have freed it at program exit, since no pointer to it exists. This is likely a symptom of having lost the pointer at some earlier point in the program.

However, it's pretty clear that I could have easily freed the block before program exit. What am I missing here? Shouldn't the memory be "still reachable"?


Solution

  • Here's an example of "definitely lost" vs. "still reachable":

    #include <stdio.h>
    #include <stdlib.h>
    
    void *p;
    
    int main()
    {
        p = malloc(10);
        p = malloc(100);
        void *m = malloc(50);
        return 0;
    }
    

    What happens in this code is the following:

    • 10 bytes are allocated and the pointer to this memory is assigned to the global p.
    • 100 bytes are allocated and the pointer to this memory is assigned to the global p. This overwrites the pointer value that pointed to 10 allocated bytes, and there is no other reference to it. So that memory is "definitely lost".
    • 50 bytes are allocated and the pointer to this memory is assigned to the local m.
    • main returns causing the lifetime of m to end. As a result there is no reference stored to the memory pointer to 50 bytes so that memory is "definitely lost".
    • The pointer value pointing to 100 bytes still lives in the global p, so cleanup routines such as those called by atexit or other compiler specific methods could still clean up that memory. So it is "still reachable".

    When running valgrind on this code, it outputs the following:

    ==60822== Memcheck, a memory error detector
    ==60822== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
    ==60822== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info
    ==60822== Command: ./x1
    ==60822== 
    ==60822== 
    ==60822== HEAP SUMMARY:
    ==60822==     in use at exit: 160 bytes in 3 blocks
    ==60822==   total heap usage: 3 allocs, 0 frees, 160 bytes allocated
    ==60822== 
    ==60822== LEAK SUMMARY:
    ==60822==    definitely lost: 60 bytes in 2 blocks
    ==60822==    indirectly lost: 0 bytes in 0 blocks
    ==60822==      possibly lost: 0 bytes in 0 blocks
    ==60822==    still reachable: 100 bytes in 1 blocks
    ==60822==         suppressed: 0 bytes in 0 blocks
    ==60822== Rerun with --leak-check=full to see details of leaked memory
    ==60822== 
    ==60822== For counts of detected and suppressed errors, rerun with: -v
    ==60822== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
    

    Which is consistent with the description above.

    So to summarize, memory is "still reachable" if a pointer to it is stored either in a file scope variable or is pointed to by any "still reachable" memory. Otherwise it is lost.