Home > Back-end >  Why is memcheck reporting memory leak (double linked list)?
Why is memcheck reporting memory leak (double linked list)?

Time:07-22

Working on simple double linked list.

struct list_node {
   struct list_node *prev, *next;
   int data;
};

struct list {
   struct list_node *first, *last;
};

With creation and pushing as follows:

struct list *list_create(void) {
    struct list *_list = malloc(sizeof(struct list));
    if(_list == NULL) {
        die("Memory allocation error for list_create\n");
    }
    _list->first = NULL;
    _list->last = NULL;
    return _list;
}
void list_push(struct list *list, int item_data) {
    struct list_node *_node = malloc(sizeof(struct list_node));
    if(_node == NULL) {
        die("Memory allocation error for list_push\n");
    }
    _node->prev = NULL;
    _node->next = NULL;
    _node->data = item_data;

    //empty list case
    if(list->last == NULL) {
        list->first = _node;
        list->last = _node;
    }
    else {
        _node->prev = list->last;
        list->last->next = _node;
        list->last = _node;
    }
}

One push works but two successive ones cause memory leak - of size of a node.
What am I missing here?

For a completion here is my free for the list - and I am calling it in my code:

void list_destroy(struct list *list) {
    if (list->first != NULL) {
        struct list_node *p = list->first;
        //if one node list
        if(list->first == list->last) {
            list->first = NULL;
            list->last = NULL;
            free(p);
            p = NULL;
        }
        else {
            while (p->next) {
                struct list_node *q = p;
                p = p->next;
                free(q);
                q = NULL;
            }
        }
    }
    free(list);
}

[SOLVED] Bug was in list_destroy - last time through while(p->next) left last p node not properly handled. Thanks everybody!

CodePudding user response:

The memory leaks are produced by the function list_destroy in this while loop

        while (p->next) {
            struct list_node *q = p;
            p = p->next;
            free(q);
            q = NULL;
        }

The last node of the list is not freed due to the condition of the while loop

        while (p->next) {

because for the last node the data member next is equal to NULL.

There is no sense to split the function into two parts. The function can look the following way

void list_destroy(struct list *list) {
    while ( list->first )
    {
        struct list_node *p = list->first;
        list->first = list->first->next;
        free( p );
    }

    free( list );
}   

It would be better to pass the pointer list by reference through a pointer to it as for example

void list_destroy(struct list **list) {
    struct list_node *current = ( *list )->first;

    while ( current )
    {
        struct list_node *p = current;
        current = current->next;
        free( p );
    }

    free( *list );
    *list = NULL;
}   

In this case after the function execution the pointer to the list in the caller will be equal to NULL.

Also you could write a function that clears the list. It can look the following way

void list_clear(struct list *list) {
    while ( list->first )
    {
        struct list_node *p = list->first;
        list->first = list->first->next;
        free( p );
    }

    list->last = NULL;
}   

CodePudding user response:

Vlad from Moscow wrote a cleaner version. I'd like to add "defensive programming". It's dangerous to assume create() has been called before push() or destroy(). In fact, you can avoid a tough-to-find bug (using a pointer in the calling code after it's been free()'d.) Always check 'passed-in' arguments.

In the calling code:

myList = list_destroy( myList );

and return something that can be used.

list_t *list_destroy( list_t *pList ) {
    assert( pList != NULL );
    // iterate releasing alloc'd blocks...
    return NULL;
}
  • Related