内存泄漏问题,销毁函数没有释放由我的反向函数创建的链表节点

memory leak problem, destroy function is not freeing the linked list nodes created by my reverse function

提问人:cougarhound 提问时间:11/15/2023 最后编辑:cougarhound 更新时间:11/17/2023 访问量:106

问:

我的 list_sum 函数中的 destroy_list(pHead1_reversed) 和 destroy_list(pHead2_reversed) 函数似乎没有释放节点。我遇到了内存泄漏。我做错了什么?

int main(int argc, char* argv[])
{
//add up 189 + 11

Node* head1 = NULL;
    Node* head2 = NULL;
    Node* head_sum = NULL;
    //create a list for the number 189
    head_insert(&head1, 9);
    head_insert(&head1, 8);
    head_insert(&head1, 1);
    //create a list for the number 11
    head_insert(&head2, 1);
    head_insert(&head2, 1);
    head_sum = list_sum(head1, head2);
    printf("The sum of ");
    print_list(head1);
    printf(" + ");
    print_list(head2);
    printf("\n");
    printf(" = ");
    print_list(head_sum);
    printf("\n");
    //clean up three lists
    destroy_list(head1); head1 = NULL;
    destroy_list(head2); head2 = NULL;
    destroy_list(head_sum); head_sum = NULL;
    return 0;
}
**********************************************************************
void head_insert(Node** hHead, int newItem)
{
    Node* pNode = NULL;
    pNode = (Node*)malloc(sizeof(Node));
    if (pNode == NULL)
    {
        printf("malloc failed for node \n");
        exit(1);
    }
    pNode->data = newItem;
    pNode->next = *hHead;
    *hHead = pNode;
}


void destroy_list(Node* hHead)
{
    Node* current = hHead;
    Node* next;

    while (current != NULL) {
        next = current->next;
        free(current);
        current = next;
    }
}

Node* list_sum(Node* hHead1, Node* hHead2)
{
    Node* pHead1_reversed=reverse(hHead1);
    Node* pHead2_reversed=reverse(hHead2);
    Node* result_reversed;
    Node* resultHead = NULL;
    int carry = 0;
    while (pHead1_reversed != NULL || pHead2_reversed != NULL || carry != 0) {
        int digit1 = (pHead1_reversed != NULL) ? pHead1_reversed->data : 0;
        int digit2 = (pHead2_reversed != NULL) ? pHead2_reversed->data : 0;
        int sum = digit1 + digit2 + carry;
        carry = sum / 10;
        Node* newNode = (Node*)malloc(sizeof(Node));
        if (newNode == NULL) {
            printf("malloc failed for result node \n");
            exit(1);
        }
        newNode->data = sum % 10;
        newNode->next = NULL;
        if (resultHead == NULL) {
            resultHead = newNode;
        }
        else {
            Node* temp = resultHead;
            while (temp->next != NULL) {
                temp = temp->next;
            }
            temp->next = newNode;
        }
        if (pHead1_reversed != NULL) pHead1_reversed = pHead1_reversed->next;
        if (pHead2_reversed != NULL) pHead2_reversed = pHead2_reversed->next;
    }
    destroy_list(pHead1_reversed);
    destroy_list(pHead2_reversed);
    result_reversed = reverse(resultHead);
    destroy_list(resultHead);
    return result_reversed; 
}

void print_list(Node* hHead)
{
    Node* pHead = (Node*)hHead;

    printf("*******\n");
    while (pHead != NULL)
    {
        printf("%d\n", pHead->data);
        pHead = pHead->next;
    }
    printf("*******\n");
}



Node* reverse(Node* hHead)
{
    Node* resultHead = NULL;
    Node* next;
    while (hHead != NULL) {
        Node* newNode = (Node*)malloc(sizeof(Node));
        if (newNode == NULL) {
            printf("malloc failed for result node \n");
            exit(1);
        }
        newNode->data = hHead->data;
        newNode->next = resultHead;
        resultHead = newNode;
        hHead = hHead->next;
    }
    return resultHead;
}

尝试将pHead2_reversed设置为NULL,但不起作用。还尝试直接在反向函数中释放节点,而不是使用函数调用。顶部是我的主驱动程序文件,底部是函数实现文件。

c 数据结构 链接列表

评论

3赞 Some programmer dude 11/15/2023
我建议你拿出一支铅笔和一些纸。然后在纸上画两个小列表,使用标记的方块表示节点(和其他变量),使用箭头表示指针/链接。然后擦除并重新绘制箭头以找出如何解决您的问题。一旦解决方案在纸上起作用,就实施它。
2赞 Some programmer dude 11/15/2023
当您遇到问题并且需要调试程序时,在调试器中单步执行代码时,请再次使用铅笔和纸,以查看代码的真正作用。
1赞 Allan Wind 11/15/2023
您的程序不完整,不包含相关标头和类型定义。
0赞 Allan Wind 11/15/2023
不要从 中强制转换 void *。malloc()
0赞 Allan Wind 11/15/2023
如果您使用的是 Linux,那么寻找泄漏的好工具是 valgrind。

答:

3赞 trincot 11/15/2023 #1

您在以下位置泄漏内存:list_sum

虽然你有:

destroy_list(pHead1_reversed);
destroy_list(pHead2_reversed);

...这些指针不指向这些反向列表的头部,因为您在前面的循环中将它们向前移动:

    if (pHead1_reversed != NULL) pHead1_reversed = pHead1_reversed->next;
    if (pHead2_reversed != NULL) pHead2_reversed = pHead2_reversed->next;

在循环之前,必须保留对这些头的引用,然后从这些头节点开始释放列表。

备注

通过为链表定义单独的类型(结构),可以更好地避免这种情况的发生,该类型将列表的头部作为成员。这样可以更容易地保证这个头确实是链表的头。释放链表的函数与释放单个节点的函数不同。

其次,如果每次都必须找到列表的末尾才能附加到结果列表中,然后在末尾反转该列表,则效率不高。更有效的是,通过在结果列表的前面插入总和数字,立即以正确的顺序创建结果。reverse

您也可以在构建总和的同时销毁反转列表:在同一循环中。这可能会导致非常优雅的代码。

最好只使用一个函数来分配节点。目前,此代码分布在三个位置。

建议的代码

下面是一些代码,显示了如何在考虑上述备注的情况下完成它:

#include <stdio.h>
#include <stdlib.h>

//////////////////////// Node /////////////////////////////////

typedef struct node {
    int data;
    struct node *next; 
} Node;

Node* create_node(int data, Node *next_node) {
    Node* node = malloc(sizeof(*node));
    if (node== NULL) {
        printf("malloc failed for creating node with data %d\n", data);
        exit(1);
    }
    node->data = data;
    node->next = next_node;
    return node;
}

Node* destroy_node(Node* node) // Returns the successor
{
    Node* next = node->next;
    free(node);
    return next;
}

//////////////////////// Linked List /////////////////////////////////

typedef struct linkedlist {
    Node *head; 
} LinkedList;

LinkedList* create_list() {
    LinkedList* list = malloc(sizeof(*list));
    if (list == NULL) {
        printf("malloc failed for creating list\n");
        exit(1);
    }
    list->head = NULL;
    return list;
}

int list_is_empty(LinkedList *list) {
    return list->head ? 0 : 1;
}

int list_pop_head(LinkedList *list, int defaultValue)
{
    if (list_is_empty(list)) {
        return defaultValue;
    }
    int data = list->head->data;
    list->head = destroy_node(list->head);
    return data;
}

void destroy_list(LinkedList *list)
{
    while (!list_is_empty(list)) {
        list_pop_head(list, 0);
    }
}

void list_insert_head(LinkedList *list, int data)
{
    list->head = create_node(data, list->head);
}

LinkedList* list_reverse(LinkedList* list)
{
    LinkedList *result = create_list();
    for (Node* node = list->head; node; node = node->next) {
        list_insert_head(result, node->data);
    }
    return result;
}

LinkedList* list_sum(LinkedList* list1, LinkedList* list2)
{
    LinkedList* reversed1 = list_reverse(list1);
    LinkedList* reversed2 = list_reverse(list2);
    LinkedList* result = create_list();
    int carry = 0;
    
    while (!list_is_empty(reversed1) || !list_is_empty(reversed2) || carry) {
        int sum = list_pop_head(reversed1, 0) + list_pop_head(reversed2, 0) + carry;
        carry = sum / 10;
        list_insert_head(result, sum % 10);
    }
    return result;
}

void list_print(LinkedList* list)
{
    for (Node* node = list->head; node; node = node->next) {
       printf("%d -> ", node->data);
    }
    printf("NULL\n");
}

int main(int argc, char* argv[])
{
    //add up 189 + 11
    LinkedList* list1 = create_list();
    LinkedList* list2 = create_list();
    //populate a list for the number 189
    list_insert_head(list1, 9);
    list_insert_head(list1, 8);
    list_insert_head(list1, 1);
    //populate a list for the number 11
    list_insert_head(list2, 1);
    list_insert_head(list2, 1);
    LinkedList *head_sum = list_sum(list1, list2);
    printf("The sum of\n");
    list_print(list1);
    printf("+\n");
    list_print(list2);
    printf("=\n");
    list_print(head_sum);
    //clean up three lists
    destroy_list(list1);
    destroy_list(list2);
    destroy_list(head_sum);
    return 0;
}

评论

0赞 Allan Wind 11/17/2023
list_pop_head()在空列表中返回 0 允许干净地实现,但具有重叠的值和错误域是一种错误修剪设计。如果 op 需要实现乘法,则恒等值不再是 0 而是 1,您需要检查并引入与 sum 相比的不对称性。现在可以进行设计,如果需要,将来可以更改设计。list_sum()list_is_empty()
0赞 trincot 11/17/2023
@AllanWind,点了。更新以包含此默认值的参数。
0赞 Allan Wind 11/17/2023
很好的更改,但通常这需要调用方知道存储了哪个值,以确定合适的单独错误值。这可能很好,尤其是对于此用例。最好将这两个域分开。例如,返回错误并对值使用 out 参数(或相反的参数,或返回错误或值等的标记联合)。
0赞 trincot 11/17/2023
“这需要调用方知道存储了哪个值,以确定合适的单独错误值”:是的,我不明白为什么调用方不知道他们想要获取哪个值作为默认值。所以我认为这里没有问题。我打算 pop 功能始终返回一个整数,而不是将空堆栈视为 pop 起作用的错误条件。相反,我认为在这里对空堆栈的调用是一个有意的功能。如果我们想有一个 pop 函数,在空列表上完成时它被认为是错误,那么是的,最好有一个不同的设计。
0赞 Allan Wind 11/17/2023
我会让调用方生成默认值而不是列表(因为它混合了两件事),但您绝对正确,让调用方控制默认值是一个很好的更改。这是您的规范,因此可以使用 return 和 element 或默认值。对我来说是一个堆栈操作,而下溢是一个错误。单链表是具体的数据结构,没有找到其操作的权威来源。顺便说一句,不要求您进行任何更改,只是添加一些上下文。poppop
0赞 Allan Wind 11/15/2023 #2

解决内存泄漏的另一种方法是就地反转列表。如果更改为 take a 并返回新分配的节点,则它将成为更通用的函数。然后,您可以重用 ,使用为您反转列表的事实,并合并两个重复的 NULL 检查。为了获得良好的度量值,请使用 Replace 和 Short 变量名称:list_sum()head_insert()Node *head_insert()list_sum()head_insert()signed charint

Node *head_insert(Node *hHead, int newItem) {
    Node* pNode = malloc(sizeof *pNode);
    if (!pNode) {
        printf("malloc failed for node \n");
        exit(1);
    }
    pNode->data = newItem;
    pNode->next = hHead;
    return pNode;
}

Node* reverse(Node* hHead) {
    if(!hHead)
        return hHead;
    Node *next = hHead->next;
    hHead->next = NULL;
    while(next) {
        Node *prev = hHead;
        hHead = next;
        next = hHead->next;
        hHead->next = prev;
    }
    return hHead;
}

Node *list_sum(Node* a, Node* b) {
    a = reverse(a);
    b = reverse(b);
    Node* result = NULL;
    signed char carry = 0;
    for(Node *a2 = a, *b2 = b; a2 || b2 || carry; ) {
        signed char a2_digit = 0;
        if(a2) {
            a2_digit = a2->data;
            a2 = a2->next;
        }
        signed char b2_digit = 0;
        if(b2) {
            b2_digit = b2->data;
            b2 = b2->next;
        }
        signed char sum = a2_digit + b2_digit + carry;
        carry = sum / 10;
        result = head_insert(result, sum % 10);
    }
    reverse(a);
    reverse(b);
    return result;
}

int main(void) {
    //add up 189 + 11
    Node* head1 = NULL;
    Node* head2 = NULL;
    Node* head_sum = NULL;
    //create a list for the number 189
    head1 = head_insert(head1, 9);
    head1 = head_insert(head1, 8);
    head1 = head_insert(head1, 1);
    //create a list for the number 11
    head2 = head_insert(head2, 1);
    head2 = head_insert(head2, 1);
    head_sum = list_sum(head1, head2);
    printf("The sum of ");
    print_list(head1);
    printf(" + ");
    print_list(head2);
    printf(" = ");
    print_list(head_sum);
    printf("\n");
    //clean up three lists
    destroy_list(head1);
    destroy_list(head2);
    destroy_list(head_sum);
}

和示例运行方式(我删除了以使输出更紧凑):valgrind --leak-check=full\nprint_list()

The sum of 189 + 11 = 200
[...]
==3546== HEAP SUMMARY:
==3546==     in use at exit: 0 bytes in 0 blocks
==3546==   total heap usage: 9 allocs, 9 frees, 1,152 bytes allocated
==3546== 
==3546== All heap blocks were freed -- no leaks are possible

评论

0赞 Allan Wind 11/16/2023
完全。专业提示:在软件中,尝试的东西几乎是免费的。
0赞 cougarhound 11/16/2023
我检查了 trincot 的答案,因为我必须使用 head_insert(Node** hHead, int newItem)。非常感谢。
0赞 Allan Wind 11/16/2023
@cougarhound是吗?
0赞 cougarhound 11/17/2023
没关系,我只是注意到他也没有使用 head_insert(Node** hHead, int new Item)。不知道我在说什么。
0赞 Allan Wind 11/17/2023
你可以根据我发布的那个实现原始版本(但不能反过来)。确保你预先说明这些要求,这样我的回答对你有帮助。顺便说一句,我的答案是唯一提供修复泄漏证据的答案。head_insert()