6

valgrind は、XX ブロックに XX バイトがあり、間違いなく記録が失われていることを教えてくれます。

ソースはmallocにありますが、malloc用に十分なメモリを解放していないためだと思います。とにかく、ヒープエラーを引き起こしていると思われるコードを提供しました。

私は list_remove でメモリを解放していないことを認識しています。これが問題の唯一の原因であると確信しています。おそらく温度をいくらか変更する必要がありますが、それが唯一の問題かどうかはわかりません。

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 で発生していることがわかります。ここで、新しいリストの作成を開始できます。構造体は非常に単純で、値と前、次があります。

#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));
    }
}

助けてください!それでもヒープでメモリリークエラーが発生します...

4

2 に答える 2

4

list_free() に関心がある場合は、ソースで削除チェーンを強化することをお勧めします。以下では、すべてが終了したら、*list を NULL にすることを前提としています (リスト全体が削除されたため)。

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()prevandの値を設定しないnextため、ガベージが含まれます。
  2. ここでの他のすべての関数は、(1) next と prev が正しく初期化されていることを前提としています。率直に言って、これが 2 番目の追加の開始時に失敗しないことに驚いています。

循環リスト挿入では、挿入される新しいノードが初期リスト自体である可能性があると想定する必要があります。この努力を必要以上に難しくしているようです。循環リストは任意のノードをリスト ヘッドとして持つことができることを思い出してください。これは、現在のリスト 'head' が削除された場合よりも優れているとは言えません。これが発生したときに、呼び出し元に対して新しいリスト「ヘッド」を再確立するためのメカニズムが整っている必要があります。これと同じメカニズムにより、最後のノードが削除されたときにリスト ヘッドを NULL に設定できるようにする必要があります。

あなたのコードは、ポインターへのポインターを使用せずにこれを行うことを明白に試みているように見えますが、循環リンクリストのタスクを非常に簡単にします。コード内のその他の注意事項:

  • あなたの関数のほとんどは、戻り値によってリストヘッドがどうあるべきかを呼び出し元に提案しようとしているようです。むしろ、in/out パラメータで強制する必要があります。
  • のノードに関連する新しいノードを挿入する関数は、新しいノードを返す必要があります。
  • 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 で実行しましたが、リークは見つかりませんでした。私はそれがあなたのニーズにすぐに適合しないことを確信してます** 。

于 2012-11-12T13:49:25.573 に答える
1

コードは問題ないようです。

はどのようにlist_t定義されていますか? list_t動的に割り当てられたメモリを参照するメンバーはありますか? もしそうなら、おそらくそれらによって参照されているメモリも解放する必要があります。

于 2012-11-12T12:41:58.400 に答える