可疑的内存管理影响

时间:2017-07-13 11:29:11

标签: c pointers memory

因此,为了巩固我对C中指针和内存管理的理解,我决定对链表进行基本实现。

我对我的代码中的某些内容非常怀疑,并且想知道我是否可以得到一些澄清。首先,代码:

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

//Doubly-linked lists brah
typedef struct ll_int {
    struct ll_int* prev;
    int value;
    struct ll_int* next;
} ll_int_t;

ll_int_t* newNode(int value) {
    //Allocate the memory
    ll_int_t* new = (ll_int_t*) malloc(sizeof(ll_int_t));

    //If it was not successfully created, exit
    if (new == NULL) {
        fprintf(stderr, "Unable to allocate memory for new LL node\n");
        exit(EXIT_FAILURE);
    }

    //Set up the node
    new->value = value;
    new->prev = NULL;
    new->next = NULL;
    return new;
}

void addElement(ll_int_t* head, int value) {
    //Check if the head is in fact the end of the list
    if (head->next == NULL) {
        ll_int_t* new = newNode(value);
        head->next = new;
        new->prev = head;
    } else { 
        addElement(head->next, value);
    }
}

int lenList(ll_int_t* head) {
    int i = 1;
    ll_int_t* curr = head;
    while ((curr = curr->next) != NULL) i++;
    free(curr);
    return i;
}

void delHead(ll_int_t** list) {
    if (*list != NULL && lenList(*list) > 1) {
        ll_int_t* dead = *list;
        *list = (*list)->next;
        (*list)->prev = NULL;
        free(dead);
    }
}

bool delElementAt(ll_int_t** list, int pos) {
    if (pos == 0) {
        delHead(list);
        return true;
    } else {
        //TODO: Implement
    }
}

void printLL(ll_int_t** list) {
    //We could be clever and traverse back to the start if we're not
    //given the head of the list... but that's /effort/
    if ((*list)->prev != NULL) {
        printf("That is not the head of the Linked List!\n");
        return;
    }

    int i = 0;
    ll_int_t* curr = *list;
    do {
        printf("(%d): %d\n", i++, curr->value);
    } while((curr = curr->next) != NULL); //sneaky
    free(curr);
}

int main (int argc, char** argv) {
    ll_int_t* head = newNode(5);
    addElement(head, 10);
    printf("lenList: %d\n", lenList(head));
    addElement(head, 15);
    printf("lenList: %d\n", lenList(head));
    addElement(head, 20);
    printf("lenList: %d\n", lenList(head));
    printLL(&head);
    delElementAt(&head, 0);
    printLL(&head);
    return(EXIT_SUCCESS);
}

让我挑选一些痛点:

所以最初,我的addElement函数看起来像:

void addElement(ll_int_t* head, int value) {
    //Attempt to create the new node
    ll_int_t* new = newNode(value);

    //Check if the head is in fact the end of the list
    if (head->next == NULL) {
        head->next = new;
        new->prev = head;
    } else { 
        ll_int_t* curr = head->next;
        while (curr->next != NULL) curr = curr->next;
        curr->next = new;
        new->prev = curr;
    }
}

但我担心ll_int_t* curr,因为它感觉像是一个不必要的副本。所以我把它改为使用尾递归:

void addElement(ll_int_t* head, int value) {
    //Check if the head is in fact the end of the list
    if (head->next == NULL) {
        ll_int_t* new = newNode(value);
        head->next = new;
        new->prev = head;
    } else { 
        addElement(head->next, value);
    }
}

这让我感到更快乐,但我想我的问题是,我真的在这里取得了什么成就吗?

此外,free()lenList中的printLL实际上是否必要,或者功能范围是否为我处理此问题?

2 个答案:

答案 0 :(得分:4)

如果您之前没有使用valgrind工具,这是一个学习如何自信地调试内存泄漏的绝佳机会。去安装valgrind。它会告诉你是否有内存泄漏。

[~]$ valgrind --leak-check=full ./test
==12880== Memcheck, a memory error detector
==12880== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==12880== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==12880== Command: ./test
==12880== 
lenList: 2
lenList: 3
lenList: 4
(0): 5
(1): 10
(2): 15
(3): 20
(0): 10
(1): 15
(2): 20
==12880== 
==12880== HEAP SUMMARY:
==12880==     in use at exit: 72 bytes in 3 blocks
==12880==   total heap usage: 4 allocs, 1 frees, 96 bytes allocated
==12880== 
==12880== 72 (24 direct, 48 indirect) bytes in 1 blocks are definitely lost in loss record 3 of 3
==12880==    at 0x4C27BE3: malloc (vg_replace_malloc.c:299)
==12880==    by 0x4006F1: newNode (test.c:14)
==12880==    by 0x400764: addElement (test.c:31)
==12880==    by 0x40094B: main (test.c:89)
==12880== 
==12880== LEAK SUMMARY:
==12880==    definitely lost: 24 bytes in 1 blocks
==12880==    indirectly lost: 48 bytes in 2 blocks
==12880==      possibly lost: 0 bytes in 0 blocks
==12880==    still reachable: 0 bytes in 0 blocks
==12880==         suppressed: 0 bytes in 0 blocks
==12880== 
==12880== For counts of detected and suppressed errors, rerun with: -v
==12880== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

答案 1 :(得分:2)

现在你最大的问题不是泄密,而是free - 垃圾邮件。只有在修复之后才会有泄漏,然后你可以在正确的位置修复它。

您的代码中有两个位置正在销毁链接列表中的节点,并且您应该在一个地方,但不方便地丢失。通过在适当的地方使用const-ness可以更好地检测前两个。例如:

int lenList(ll_int_t* head) {
    int i = 1;
    ll_int_t* curr = head;
    while ((curr = curr->next) != NULL) i++;
    free(curr); // ????????
    return i;
}

这是一个简单的(尽管效率低下)长度计算功能。不应以任何方式修改列表本身。因此,输入指针指向const。

int lenList(const ll_int_t *head) {
    int i = 1;
    ll_int_t* curr = head; // Warning: const-ness disqualified
    while ((curr = curr->next) != NULL) i++;
    free(curr);
    return i;
}

现在当你尝试编译它时,你会得到至少一个警告,告诉你在分配给非常量指针时head的常量已被丢弃({ {1}})。编译时将警告视为错误(您应该 始终 执行此操作)将因此对此进行红色标记。而且,fyi,这个功能可以大大简化为:

curr

接下来是int lenList(const ll_int_t *head) { int i = 0; for (; head; ++i, head = head->next); return i; } 函数,它的功能更加相同。

printLL

这是与以前相同的问题,这里提到的所有内容都适用于此,包括简化。此外,该功能通过地址不必要地传递头指针,这最终是没有意义的。就像以前一样,只需要一个简单的指向const的指针,它可以在不影响调用者的情况下自由修改:

void printLL(ll_int_t **list) {
    //We could be clever and traverse back to the start if we're not
    //given the head of the list... but that's /effort/
    if ((*list)->prev != NULL) {
        printf("That is not the head of the Linked List!\n");
        return;
    }

    int i = 0;
    ll_int_t* curr = *list;
    do {
        printf("(%d): %d\n", i++, curr->value);
    } while((curr = curr->next) != NULL); //sneaky
    free(curr); // ????????????
}

有了这些,你需要一个void printLL(const ll_int_t *list) { if (list && list->prev) { printf("That is not the head of the Linked List!\n"); return; } for (int i=0; list; list = list->next) printf("(%d): %d\n", i++, list->value); } 函数,给定一个正确构造列表的地址,将正确地销毁它。可以这样做的一种方法是:

freeList

当你真的要销毁整个列表时,这会派上用场。

接下来,void freeList(ll_int_t **head) { if ((*head) && (*head)->prev) { printf("That is not the head of the Linked List!\n"); return; } while (*head) { void *p = *head; *head = (*head)->next; free(p); } } vs delHead的实施。你正在倒退他们。前者应该用后者实现,而不是相反。例如:

delElementAt

最后,bool delElementAt(ll_int_t** list, int pos) { bool res = false; ll_int_t* prev = NULL; while (*list && pos--) { prev = *list; list = &(*list)->next; } if (*list) { void *p = *list; *list = (*list)->next; (*list)->prev = prev; free(p); res = true; } return res; } void delHead(ll_int_t** list) { delElementAt(list, 0); } 函数似乎只是附加到列表中。如果您在自己的结构中包含链接列表,则有更好的方法来管理此操作,我将此作为练习留给您。无论如何,这是一个通过地址传递头指针是正确的地方,因为你可以正确地初始化一个空(并因此为空)列表:

addElement

将它放在一起

最终代码如下所示,包括退出时释放列表的附录。这应该通过valgrind无泄漏:

void addElement(ll_int_t **head, int value)
{
    ll_int_t *prev = NULL;
    while (*head)
    {
        prev = *head;
        head = &(*head)->next;
    }

    *head = newNode(value);
    (*head)->prev = prev;
}

输出(指针值明显不同)

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

//Doubly-linked lists brah
typedef struct ll_int {
    struct ll_int* prev;
    int value;
    struct ll_int* next;
} ll_int_t;

ll_int_t* newNode(int value)
{
    //Allocate the memory
    ll_int_t* new = malloc(sizeof *new);

    //If it was not successfully created, exit
    if (new == NULL) {
        fprintf(stderr, "Unable to allocate memory for new LL node\n");
        exit(EXIT_FAILURE);
    }

    //Set up the node
    new->value = value;
    new->prev = NULL;
    new->next = NULL;
    return new;
}


void addElement(ll_int_t **head, int value)
{
    ll_int_t *prev = NULL;
    while (*head)
    {
        prev = *head;
        head = &(*head)->next;
    }

    *head = newNode(value);
    (*head)->prev = prev;
}


int lenList(const ll_int_t *head)
{
    int i = 0;
    for (; head; ++i, head = head->next);
    return i;
}


bool delElementAt(ll_int_t** list, int pos)
{
    bool res = false;
    ll_int_t* prev = NULL;

    while (*list && pos--)
    {
        prev = *list;
        list = &(*list)->next;
    }

    if (*list)
    {
        void *p = *list;
        *list = (*list)->next;
        (*list)->prev = prev;
        free(p);
        res = true;
    }
    return res;
}


void delHead(ll_int_t** list)
{
    delElementAt(list, 0);
}


void freeList(ll_int_t **head)
{
    if ((*head) && (*head)->prev) {
        printf("That is not the head of the Linked List!\n");
        return;
    }

    while (*head)
    {
        void *p = *head;
        *head = (*head)->next;
        free(p);
    }
}


void printLL(const ll_int_t* list)
{
    if (list && list->prev) {
        printf("That is not the head of the Linked List!\n");
        return;
    }

    for (int i=0; list; list = list->next)
        printf("(%d): %d\t%8p : %8p : %8p\n", i++, list->value, list->prev, list, list->next);
}

int main (int argc, char** argv)
{
    ll_int_t* head = NULL;

    addElement(&head, 5);
    addElement(&head, 10);
    printf("lenList: %d\n", lenList(head));

    addElement(&head, 15);
    printf("lenList: %d\n", lenList(head));

    addElement(&head, 20);
    printf("lenList: %d\n", lenList(head));

    printLL(head);
    delElementAt(&head, 1);

    printLL(head);
    delHead(&head);

    printLL(head);
    freeList(&head);

    return(EXIT_SUCCESS);
}