简体   繁体   中英

Valgrind detects reachable leaks, where are they?

I have written a small implementation of a simple stack in C using a linked list. The application works after a bit of fussing with gdb and valgrind. However, I have noticed that valgrind reports a number of "still reachable" leaks at termination of the program.

After some googling, I have found that these type of leaks are not an issue and that I generally shouldn't worry about them. Well that's great news! The only thing is, I would REALLY like to understand why valgrind sees these leaks and where they appear. I went to great pains through my first pass at this application to diligently release any allocated memory. What did I miss? Where are these allocations that valgrind sees but I cannot?

I have been staring at this for awhile now, and despite fiddling with the code this way and that, have come up empty handed. Hopefully you all can help me in this endeavor.

I have stripped the app down to a minimal example that will present the leak I am observing. It just initializes the stack and then immediately destroys it and exits.

// simple stack implementation
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>

/*--- data structure in a stack item ---*/
struct data {
    char * message;
};

/*--- stack item structure ---*/
struct item {
    struct data * my_data;
    struct item * next;
};

/*--- stack data structure ---*/
struct stack {
    int initialized;
    int empty;
    struct item * head;
};
struct stack * my_stack;

/*--- create a new stack data structure with provided initial data ---*/
int
create_stack(struct data  ** initial_items, int num_items) {

    //allocate memory for the stack structure itself
    my_stack = (struct stack *) malloc(sizeof(struct stack));
    if(!my_stack) return -1;
    my_stack->empty = 1;
    my_stack ->initialized = 0;

    if(num_items) {
        //allocate memory for the head of the list first
        my_stack->head = (struct item *) malloc(sizeof(struct item));
        if(!my_stack->head) return -1;
        my_stack->head->my_data = initial_items[0];
        my_stack->head->next = NULL;
        struct item * tracker = my_stack->head;

        //fill the stack with elements containing the provided data
        int i = 1;
        for(; i < (num_items); i++) {
            tracker->next = (struct item *) malloc(sizeof(struct item));
            tracker = tracker->next;
            if(!tracker) return -1;
            tracker->my_data = initial_items[i];
            tracker->next = NULL;
        }

        //no longer empty
        my_stack->empty = 0;
    }

    //set initialized flag & return
    my_stack->initialized = 1;
    return 0;
}

/*--- destroy the stack by recursively freeing nodes ---*/
int
destroy_stack(struct item * recurse) {

    //if the starting node is valid, begin
    if(recurse) {
        //if the current node links to another
        if(recurse->next)
            //recurse on the next node
            destroy_stack(recurse->next);
        else
            //otherwise we hit the end, free the node
            free(recurse);
    }
    //the starting node is invalid, just free the stack itself
    else {
        free(my_stack);
    }

    return 0;
}

/*--- test wrapper ---*/
int
main(int argc, char **argv) {

    //initialize 10 element list of data structures to fill the stack items with
    int i = 0;
    int length = 10;
    struct data ** initial_values = (struct data **) malloc(length*sizeof(struct data *));

    //set each data element's value to i
    for(; i < length; i++) {
        char temp[16];
        sprintf(temp, "%d", i);
        initial_values[i] = (struct data *) malloc(sizeof(struct data));
        initial_values[i]->message = strdup(temp);
    }

    //simple test case
    //create a stack with the generated initial data, then destroy it
    assert( !create_stack(initial_values, length) );
    assert( !destroy_stack(my_stack->head) );

    //free the string data our stack nodes were pointing to
    i = 0;
    while(i < length) {
        free(initial_values[i]->message);
        free(initial_values[i]);
        i += 1;
    }
    free(initial_values);

    return 0;
}

Running this through valgrind produces the following identified, unreleased blocks:

(trusty)kin3tix@localhost:~/C Practice/Data Structures$ valgrind --leak-check=full --show-leak-kinds=all ./stack_leak_test
==19340== Memcheck, a memory error detector
==19340== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==19340== Using Valgrind-3.10.0.SVN and LibVEX; rerun with -h for copyright info
==19340== Command: ./stack_leak_test
==19340== 
==19340== 
==19340== HEAP SUMMARY:
==19340==     in use at exit: 168 bytes in 10 blocks
==19340==   total heap usage: 32 allocs, 22 frees, 364 bytes allocated
==19340== 
==19340== 16 bytes in 1 blocks are still reachable in loss record 1 of 3
==19340==    at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==19340==    by 0x400739: create_stack (stack_leak_test.c:40)
==19340==    by 0x40093B: main (stack_leak_test.c:95)
==19340== 
==19340== 24 bytes in 1 blocks are still reachable in loss record 2 of 3
==19340==    at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==19340==    by 0x4006E6: create_stack (stack_leak_test.c:33)
==19340==    by 0x40093B: main (stack_leak_test.c:95)
==19340== 
==19340== 128 bytes in 8 blocks are still reachable in loss record 3 of 3
==19340==    at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==19340==    by 0x4007A1: create_stack (stack_leak_test.c:48)
==19340==    by 0x40093B: main (stack_leak_test.c:95)
==19340== 
==19340== LEAK SUMMARY:
==19340==    definitely lost: 0 bytes in 0 blocks
==19340==    indirectly lost: 0 bytes in 0 blocks
==19340==      possibly lost: 0 bytes in 0 blocks
==19340==    still reachable: 168 bytes in 10 blocks
==19340==         suppressed: 0 bytes in 0 blocks
==19340== 
==19340== For counts of detected and suppressed errors, rerun with: -v
==19340== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

They all seem to stem from "create_stack" but as far as I can tell my destroy function should be catching everything I allocate. What am I missing here?

Your destruction logic is trying to recursively combine two tasks (deleting the stack object and its items) in a single function. Worse, the else state shouldn't be there at all . You already determined you're sitting on a dynamic node ( recurse ). It needs to be deleted. Whether it has a next or not is not for the current depth to decide; that will be taken care of in the next depth of recursion.

Honestly, this is much simpler if you create a helper to wipe out the linked list, and as separate wrapper to do said-same to the stack itself:

/*--- destroy linked list nodes recursively ---*/
static void destroy_stack_items(struct item *item)
{
    if (item)
    {
        destroy_stack_items(item->next);
        free(item);
    }
}

/*--- destroy the stack by recursively freeing nodes ---*/
int destroy_stack(struct stack *s)
{
    if (s)
    {
        destroy_stack_items(s->head);
        free(s);
    }
    return 0
}

and simply invoked as:

destroy_stack(my_stack);

Personally I would do it iteratively (thus remove the helper in the process), but I'm sure you have your reasons for otherwise. For example:

/*--- destroy the stack by iteratively freeing nodes ---*/
int destroy_stack(struct stack *s)
{
    if (s)
    {
        while (s->head)
        {
            struct item *tmp = s->head;
            s->head = tmp->next;
            free(tmp);
        }
        free(s);
    }
    return 0
}

Best of luck.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM