【问题标题】:Error deleting node from first position in c从 c 中的第一个位置删除节点时出错
【发布时间】:2016-08-12 09:24:45
【问题描述】:

正如我之前的许多帖子所示,我正在编写代码来模拟疯狂的 8 人纸牌游戏。我有一个删除节点功能,旨在从正在播放的牌组中删除卡片。它适用于第一张之后的卡片,但是每次我尝试从列表中删除第一张卡片(节点)时,它都不会删除,然后会弄乱整个程序。这是函数:

void deleteNode(card *head, int coordinate) {

    card *current = head;
    card *temp = NULL;
    temp = current;
    int count = 1;
    while (head != NULL) {
        if (coordinate == 0) {
            current = current->listp;
            free(temp);
            break;
        }
        else if (count == coordinate) {
            temp = current->listp;
            current->listp = current->listp->listp;
            free(temp);
            break;
        }
        else {
            count++;
            current = current->listp;
        }
    }
}

传入它的 *head 是正在播放的手牌的顶部。坐标是用户想要玩的牌张数。例如,如果他们牌组中的第一张牌是红心 Q,而这正是他们想要玩的,他们会输入一张。在函数调用中,我从用户选择中减去 1,以便它与列表匹配(因为列表从 0 开始)。任何帮助,将不胜感激。在我解决这个问题之前,我无法继续我的项目!

【问题讨论】:

  • 必须是void deleteNode(card **head,...,因为删除第一个节点会导致列表地址发生变化(即列表地址是第一个节点的地址)
  • head == null 什么时候?

标签: c linked-list


【解决方案1】:

如 cmets 中所述,您遇到的问题是由于没有将列表的 address-of 传递给 deleteNode 函数。这个问题是基本的,但它吸引了很多人。链表的地址是第一个节点的地址。所以在删除第一个节点时,必须给下一个节点地址设置一个新的列表地址,才能让列表继续运行。

当你传递一个指向函数的指针时,例如void deleteNode(card *head, ...,函数deleteNode 接收指针head副本。副本有一个自己的地址,该地址与调用函数中指针的地址无关。 deleteNode中指针的与调用者中相同,但地址完全不同。

当您删除deleteNode 中的第一个节点时会出现问题。内存为free'd,deleteNode 函数返回。现在回到调用者(大概是main()),下次您尝试访问head 时——bam! 段错误。为什么? head 的地址从未在main 中更新,所以它仍然指向原始节点——您刚刚对deleteNode 中原始节点的内存做了什么? (你在指向持有第一个节点的内存块的指针上调用了free——它已经消失了......)

要解决此问题,只需将列表 (head) 的地址 传递给deleteNode。 (例如void deleteNode(card **head, ...)。然后你在head地址上操作(例如一个pointer-to-the-pointer-head)。现在,在删除第一个节点之前,您可以设置 *head = head->listp; 并将新的列表地址反映在调用函数 (main()) 中。例如,您的代码可以写成:

void delnode (card **head, int coordinate)
{
    card *current = *head;
    card *victim = NULL;
    victim = current;
    int count = 1;

    while (current != NULL) {
        if (coordinate == 0) {
            *head = current->listp;
            free (victim);
            break;
        }
        else if (count == coordinate) {
            victim = current->listp;
            current->listp = current->listp->listp;
            free (victim);
            break;
        }
        else {
            count++;
            current = current->listp;
        }
    }
}

但是,您可以轻松地对函数的逻辑进行一些改进。例如

void delnode (card **head, int coordinate)
{
    card *current = *head;
    card *victim = current;
    int count = 1;

    if (coordinate == 0) {
        *head = current->listp;
        free (victim);
        return;
    }

    while (current != NULL)
    {
        if (count == coordinate) {
            victim = current->listp;
            current->listp = current->listp->listp;
            free (victim);
            return;
        }
        count++;
        current = current->listp;
    }
}

最后,访问描述How to Ask a QuestionHow to create a Minimal, Complete, and Verifiable example 的链接。提供必要的详细信息,包括您的代码和相关错误(如果有),将让这里的每个人都能帮助您解决问题。

这个问题就是一个很好的例子。对于帮助您并实际编译和确认问题或答案的任何人,您要求这里的人们编写一个示例程序,该程序对您的潜在列表结构可能是什么做出有根据的猜测。当您在这里提出问题时,提供 MCVE 的目的是让其他人可以编译您的代码并确认您遇到的问题,如果需要,通过调试器运行编译后的代码以帮助您。如果您遵循帮助我们帮助您的网站的最低建议和规则,您将获得更多帮助和更多积极响应。

话虽如此,你可以用这个小示例代码来确认你的删除操作。

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

typedef struct card {
    int cardno;
    struct card *listp;
} card;

card *createnode (int c);
card *insert (card **list, int c);
void prnlist (card *list);
void delnode (card **head, int coordinate);
void dellist (card *list);
void *xcalloc (size_t nmemb, size_t sz);

int main (void) {

    card *list = NULL;

    insert (&list, 18);     /* insert test nodes */
    insert (&list, 6);
    insert (&list, 54);
    insert (&list, 12);
    insert (&list, 60);
    insert (&list, 30);

    printf ("\noriginal list:\n");
    prnlist (list);

    printf ("\ndeleting node: 2\ndeleting node: 0\n");
    delnode (&list, 2);     /* delete 3rd & 1st nodes */
    delnode (&list, 0);

    printf ("\nfinal list:\n");
    prnlist (list);

    dellist (list);         /* free allocated memory */

    return 0;
}

card *createnode (int c)
{
    card *node = xcalloc (1, sizeof *node);

    node->listp = NULL;
    node->cardno = c;

    return node;
}

card *insert (card **list, int c)
{
    card *iter = *list;
    card *node = createnode (c);

    if (!*list) {       /* add 1st node to list */
        *list = node;
        return *list;
    }

    /* insert all other nodes at end */
    for (; iter->listp; iter = iter->listp) {}

    iter->listp = node;

    return *list;    
}

void prnlist (card *list)
{
    card *iter = list;
    for (; iter->listp; iter = iter->listp)
        printf (" cardno : %d\n", iter->cardno);
    printf (" cardno : %d\n", iter->cardno);
}

void delnode (card **head, int coordinate)
{
    card *current = *head;
    card *victim = current;
    int count = 1;

    if (coordinate == 0) {
        *head = current->listp;
        free (victim);
        return;
    }

    while (current != NULL)
    {
        if (count == coordinate) {
            victim = current->listp;
            current->listp = current->listp->listp;
            free (victim);
            return;
        }
        count++;
        current = current->listp;
    }
}

void dellist (card *list)
{
    card *iter = list;
    while (iter) {
        card *victim = iter;
        iter = iter->listp;
        free (victim);
    }
}

void *xcalloc (size_t nmemb, size_t sz)
{
    void *memptr = calloc (nmemb, sz);

    if (!memptr) {
        fprintf (stderr, "xcalloc() error: virtual memory exhausted.\n");
        exit (EXIT_FAILURE);
    }

    return memptr;
}

使用/输出示例

$ ./bin/lldelcard

original list:
 cardno : 18
 cardno : 6
 cardno : 54
 cardno : 12
 cardno : 60
 cardno : 30

deleting node: 2
deleting node: 0

final list:
 cardno : 6
 cardno : 12
 cardno : 60
 cardno : 30

内存错误检查

在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您有两个责任:(1) 始终保留指向内存块起始地址的指针,因此,(2) 它可以在以下情况下被释放它不再需要。

您必须使用内存错误检查程序来确保您没有写入超出/超出分配的内存块,尝试读取或基于未初始化的值进行跳转,最后确认您已释放所有您分配的内存。

对于 Linux,valgrind 是正常的选择。有许多微妙的方法可以滥用新的内存块。使用内存错误检查器可以让您识别任何问题并验证您分配的内存的正确使用,而不是通过segfault 发现问题存在。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。

$ valgrind ./bin/lldelcard
==9094== Memcheck, a memory error detector
==9094== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==9094== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==9094== Command: ./bin/lldelcard
==9094==

original list:
 cardno : 18
 cardno : 6
 cardno : 54
 cardno : 12
 cardno : 60
 cardno : 30

deleting node: 2
deleting node: 0

final list:
 cardno : 6
 cardno : 12
 cardno : 60
 cardno : 30
==9094==
==9094== HEAP SUMMARY:
==9094==     in use at exit: 0 bytes in 0 blocks
==9094==   total heap usage: 6 allocs, 6 frees, 96 bytes allocated
==9094==
==9094== All heap blocks were freed -- no leaks are possible
==9094==
==9094== For counts of detected and suppressed errors, rerun with: -v
==9094== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 1 from 1)

始终确认所有堆块都已释放——不可能有泄漏,同样重要的是错误摘要:0 个上下文中的 0 个错误

祝你编码顺利。

【讨论】:

  • 非常感谢您的帮助!指向指针的指针是我尚未使用过的东西,但它肯定有助于我改进这段代码。
  • 很高兴它有帮助。学习 C 是一个缓慢的学习过程,但值得付出努力。指针只不过是一个变量,它保存其他东西的地址作为它的值。 (这是一级间接)。 pointer-to-pointer 只不过是一个变量,它保存 pointer 的地址作为其值(这是第二级间接)。单指针、一级间接、双指针、二级间接等等……关键是要明白学习C需要多一点时间,慢点,享受学习。
猜你喜欢
  • 2015-10-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-18
  • 2015-01-26
相关资源
最近更新 更多