在循环双向链表中释放内存

6

Valgrind告诉我有XX个内存块丢失,总共XX字节,记录了blablabla。

而且源代码中使用了malloc函数,但我认为这是因为我没有为malloc函数释放足够的内存。无论如何,我提供了我认为导致堆错误的代码。

我意识到在list_remove函数中我没有释放内存,我非常确定这是问题的唯一来源。可能需要对temp进行一些移位,但我不知道是否只有这个问题。

list_t *list_remove(list_t *list, list_t *node) {
    list_t *oldnode = node;
    node->prev->next = node->next;
    node->next->prev = node->prev;
    if (list != oldnode) {
        free(oldnode);
        return list;
    } else {
         list_t *value = list->next == list ? NULL : list->next;
     free(oldnode);
        return value;
    }
}

void list_free(list_t *list) {
    if (list) {
       while (list_remove(list, list_last(list)) != NULL) {}
    } 
}

list_last简单地返回列表的最后一个节点。

编辑:对不起,我没有提供足够的信息,Kerrek SB,alk。这是代码的其余部分,你可以看到malloc在newnode中发生,在那里我可以开始创建新的列表。结构很简单,有一个值和一个prev、next:

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "ll.h"

struct list {
    char *value;
    struct list *next;
    struct list *prev;
};

const char *list_node_value(list_t *node) {
    return node->value;
}

list_t *list_first(list_t *list) {
    return list;
}

list_t *list_last(list_t *list) {
    return list->prev;
}

list_t *list_next(list_t *node) {
    return node->next;
}

list_t *list_previous(list_t *node) {
    return node->prev;
}

static void failed_allocation(void) {
    fprintf(stderr, "Out of memory.\n");
    abort();
}

static list_t *new_node(const char *value) {
    list_t *node = malloc(sizeof(list_t));
    if (!node) failed_allocation();
    node->value = malloc(strlen(value)+1);
    if (!node->value) failed_allocation();
    strcpy(node->value, value);
    return node;
}

list_t *list_insert_before(list_t *list, list_t *node, const char *value) {
    list_t *insert_node = new_node(value);
    insert_node->prev = node->prev;
    insert_node->next = node;
    insert_node->next->prev = insert_node;
    insert_node->prev->next = insert_node;
    if (list == node) {
        return insert_node;
    } else {
        return list;
    }
}

list_t *list_append(list_t *list, const char *value) {
    if (list) {
        (void) list_insert_before(list, list, value);
        return list;
    } else {
        list_t *node = new_node(value);
        node->prev = node->next = node;
        return node;
    }
}

list_t *list_prepend(list_t *list, const char *value) {
    if (list) {
        return list_insert_before(list, list, value);
    } else {
        list_t *node = new_node(value);
        node->prev = node->next = node;
        return node;
    }
}

list_t *list_remove(list_t *list, list_t *node) {
    list_t *oldnode = node;
    node->prev->next = node->next;
    node->next->prev = node->prev;
    if (list != oldnode) {
        free(oldnode);
        return list;
    } else {
         list_t *value = list->next == list ? NULL : list->next;
     free(oldnode);
        return value;
    }
}

void list_free(list_t *list) {
    if (list) {
       while (list_remove(list, list_last(list)) != NULL) {}
    } 
}

void list_foreach(list_t *list, void (*function)(const char*)) {
    if (list) {
        list_t *cur = list_first(list);
        do {
            function(cur->value);
            cur = cur->next;
        } while (cur != list_first(list));
    }
}

请帮忙!在堆中仍然出现了内存泄漏错误...


6
你需要每一个 free 对应一个 malloc。由于你的代码中没有 malloc,因此无法确定问题出在哪里。 - Kerrek SB
嘿,我在我的代码中发布了malloc,感谢您的初步反馈。您能否详细解释一下如何解决这个问题?非常感谢。 - user1818022
2个回答

4

如果您关心list_free(),我建议你在源代码中加强删除链。以下假设,当所有操作完成后,*list需要为NULL(因为整个列表已被删除)。

注:list_free() 是一个函数名,无需翻译。
void list_free(list_t **list) 
{
    if (list && *list)
    {
        list_t* next = (*list)->next;
        while (next && (next != *list))
        {
            list_t *tmp = next;
            next = next->next;
            free(tmp);
        }

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

或者类似这样。通过传递您的外部列表指针的地址来调用:

list_t *list = NULL;

.. initialize and use your list...

// free the list
list_free(&list);
编辑 在OP发布更多的代码后,有几件事情异常突出。
  1. list_newnode() 没有设置 prevnext 的值,所以它们包含垃圾。
  2. 这里的每个其他函数都假定(1)已正确初始化下一个和上一个。坦白地说,我很惊讶它不会在第二个add开始时出错。

循环列表插入必须假设要插入的新节点可以成为初始列表本身。看起来你正在做比需要的更努力。请记住,循环列表可以将任何节点作为列表头,当当前列表“head”被删除时,这一点得到了证明。必须有一种机制来重新建立新列表“head”给调用者,当这种情况发生时。这种相同的机制必须允许在删除最后一个节点时将列表头设置为NULL。

您的代码似乎试图在不使用指向指针的指针的情况下进行这样的尝试,但是它们使得循环链接列表的任务变得容易得多。您代码中其他注意事项如下:

  • 你的大多数函数似乎试图通过返回值“建议”调用者列表头应该是什么。相反,他们应该通过输入/输出参数“强制执行”。
  • 任何一个将相对于另一个插入新节点的函数都应返回新节点。
  • list_prepend()list_append() 函数应视为相对于列表头的核心插入函数。其他api(list_insert_before(),list_insert_after()等)应完全相对于您要在其之前或之后插入的有效现有节点,正如我上面所说,始终返回指向新插入节点的指针。你会看到两个非基于根的插入器函数不再传递列表头。
  • 你的大多数实用程序函数是正确的,除了在执行解除引用之前未检查无效指针之外。仍然有一些没有检查,但现在至少可以管理。

以下是围绕您的大部分功能构建的代码。实际的节点放置例程进行了更改,并尽力加以注释。主测试架很简单。如果这里有重大错误,我相信SO-watchtower会迅速指出,但代码的重点不仅仅是修复你的代码;它是一个学习的过程:

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>
#include <assert.h>

// node structure
typedef struct list_t {
    char *value;
    struct list_t *next;
    struct list_t *prev;
} list_t;

static void failed_allocation(void) {
    fprintf(stderr, "Out of memory.\n");
    abort();
}


// initialize a linked list header pointer. Just sets it to NULL.
void list_init(list_t** listpp)
{
    if (listpp)
        *listpp = NULL;
}

// return the value-field of a valid list node.
// otherwise return NULL if node is NULL.
const char *list_node_value(list_t *node)
{
    return (node ? node->value : NULL);
}

// return the next pointer (which may be a self-reference)
//  of a valid list_t pointer.
list_t *list_next(list_t *node)
{
    return (node ? node->next : NULL);
}

// return the previous pointer (which may be a self-reference)
//  of a valid list_t pointer.
list_t *list_previous(list_t *node)
{
    return (node ? node->prev : NULL);
}


// return the same pointer we were passed.
list_t *list_first(list_t *headp)
{
    return headp;
}

// return the previous pointer (which may be a self-reference)
//  of the given list-head pointer.
list_t *list_last(list_t *headp)
{
    return list_previous(headp);
}

// insert a new item at the end of the list, which means it
//  becomes the item previous to the head pointer. this handles
//  the case of an initially empty list, which creates the first
//  node that is self-referencing.
list_t *list_append(list_t **headpp, const char* value)
{
    if (!headpp) // error. must pass the address of a list_t ptr.
        return NULL;

    // allocate a new node.
    list_t* p = malloc(sizeof(*p));
    if (p == NULL)
        failed_allocation();

    // setup duplicate value
    p->value = (value) ? strdup(value) : NULL;

    // insert the node into the list. note that this
    //  works even when the head pointer is an initial
    //  self-referencing node.
    if (*headpp)
    {
        (*headpp)->prev->next = p;
        p->prev = (*headpp)->prev;
        p->next  = (*headpp);
        (*headpp)->prev = p;
    }
    else
    {   // no prior list. we're it. self-reference
        *headpp = p;
        p->next = p->prev = p;
    }
    return p;
}


// insert a new value into the list, returns a pointer to the
//  node allocated to hold the value. this will ALWAYS update
//  the given head pointer, since the new node is being prepended
//  to the list and by-definition becomes the new head.
list_t *list_prepend(list_t **headpp, const char* value)
{
    list_append(headpp, value);
    if (!(headpp && *headpp))
        return NULL;
    *headpp = (*headpp)->prev;
    return *headpp;
}


// insert a new node previous to the given valid node pointer.
// returns a pointer to the inserted node, or NULL on error.
list_t *list_insert_before(list_t* node, const char* value)
{
    // node *must* be a valid list_t pointer.
    if (!node)
        return NULL;
    list_prepend(&node, value);
    return node;
}


// insert a new node after the given valid node pointer.
// returns a pointer to the inserted node, or NULL on error.
list_t *list_insert_after(list_t* node, const char* value)
{
    // node *must* be a valid list_t pointer.
    if (!node)
        return NULL;
    node = node->next;
    list_prepend(&node, value);
    return node;
}


// delete a node referenced by the node pointer parameter.
//  this *can* be the root pointer, which means the root
//  must be set to the next item in the list before return.
int list_remove(list_t** headpp, list_t* node)
{
    // no list, empty list, or no node all return immediately.
    if (!(headpp && *headpp && node))
        return 1;

    // validate the node is in *this* list. it may seem odd, but
    //  we cannot just free it if the node may be in a *different*
    //  list, as it could be the other list's head-ptr.
    if (*headpp != node)
    {
        list_t *p = (*headpp)->next;
        while (p != node && p != *headpp)
            p = p->next;
        if (p == *headpp)
            return 1;
    }

    // isolate the node pointer by connecting surrounding links.
    node->next->prev = node->prev;
    node->prev->next = node->next;

    // move the head pointer if it is the same node
    if (*headpp ==  node)
        *headpp = (node != node->next) ? node->next : NULL;

    // finally we can delete the node.
    free(node->value);
    free(node);
    return 0;
}


// release the entire list. the list pointer will be reset to
//  NULL when this is finished.
void list_free(list_t **headpp)
{
    if (!(headpp && *headpp))
        return;
    while (*headpp)
        list_remove(headpp, *headpp);
}


// enumerate the list starting at the given node.
void list_foreach(list_t *listp, void (*function)(const char*))
{
    if (listp)
    {
        list_t *cur = listp;
        do {
            function(cur->value);
            cur = cur->next;
        } while (cur != listp);
    }
    printf("\n");
}

// printer callback
void print_str(const char* value)
{
    printf("%s\n", value);
}

// main entrypoint
int main(int argc, char *argv[])
{
    list_t *listp;
    list_init(&listp);

    // insert some new entries
    list_t* hello =   list_append(&listp, "Hello, Bedrock!!");
    assert(NULL != hello);
    assert(listp == hello);

    // insert Fred prior to hello. does not change the list head.
    list_t* fred = list_insert_before(hello, "Fred Flintstone");
    assert(NULL != fred);
    assert(listp == hello);
    // Hello, Bedrock!!
    // Fred Flintstone
    list_foreach(listp, print_str);

    // insert Wilma priot to Fred. does not change the list head.
    list_t* wilma = list_insert_before(fred, "Wilma Flintstone");
    assert(NULL != wilma);
    assert(list_next(wilma) == fred);
    assert(list_previous(wilma) == hello);
    // Hello, Bedrock!!
    // Wilma Flintstone
    // Fred Flintstone
    list_foreach(listp, print_str);

    list_t* barney =  list_prepend(&listp, "Barney Rubble");
    list_t* dino =    list_insert_after(wilma, "Dino");
    assert(barney != NULL);
    assert(dino != NULL);
    assert(listp == barney);
    assert(list_previous(barney) == fred);
    assert(list_next(barney) == hello);
    // Barney Rubble
    // Hello, Bedrock!!
    // Wilma Flintstone
    // Dino
    // Fred Flintstone
    list_foreach(listp, print_str);

    // remove everyone, one at a time.
    list_remove(&listp, fred);   // will not relocate the list head.
    // Barney Rubble
    // Hello, Bedrock!!
    // Wilma Flintstone
    // Dino
    list_foreach(listp, print_str);

    list_remove(&listp, hello);  // will not relocate the list head.
    // Barney Rubble
    // Wilma Flintstone
    // Dino
    list_foreach(listp, print_str);

    list_remove(&listp, barney); // will relocate the list head.
    // Wilma Flintstone
    // Dino
    list_foreach(listp, print_str);
    assert(listp == wilma);
    assert(list_next(wilma) == dino);
    assert(list_previous(listp) == dino);

    list_remove(&listp, wilma);  // will relocate the list head.
    // Dino
    list_foreach(listp, print_str);

    list_remove(&listp, dino);   // will relocate the list head;

    // generate a raft entries (a million of them)/
    char number[32];
    int i=0;
    for (;i<1000000; i++)
    {
        sprintf(number, "%d", i);
        list_append(&listp, number);
    }

    // now test freeing the entire list.
    list_free(&listp);

    return 0;
}

断言和转储的使用有助于验证算法的正确性。应该与代码中的注释匹配的结果输出如下:

Hello, Bedrock!!
Fred Flintstone

Hello, Bedrock!!
Wilma Flintstone
Fred Flintstone

Barney Rubble
Hello, Bedrock!!
Wilma Flintstone
Dino
Fred Flintstone

Barney Rubble
Hello, Bedrock!!
Wilma Flintstone
Dino

Barney Rubble
Wilma Flintstone
Dino

Wilma Flintstone
Dino

Dino

最后的想法:我已经通过valgrind运行了这个程序并且没有发现泄漏。我相信它不能完全适合你的需求。其中大部分(其中一半已经实现)会。


嘿,WhozCraig,谢谢你的回复!我刚试了一下你的方法,但它不起作用,可能是因为它与结构体不兼容。然而,我已经编辑了我的原始帖子并提供了其余的代码。请帮忙! :) - user1818022
@user1818022 我会看一下。很有可能是我对 list_t 类型定义的理解与您定义的不兼容。 - WhozCraig
那么我只需要将prev和next设置为它本身吗?这样就可以解决问题了吗? - user1818022

1

代码看起来没问题。

list_t 是如何定义的?list_t 是否有任何引用动态分配内存的成员?如果有,您可能还需要释放这些成员引用的内存。


嘿,Alk!感谢你回复我。我已经编辑了我的原始帖子,里面包含其余的代码,请看一下! - user1818022

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接