【问题标题】:How is this singly linked list destructor causing an infinite loop?这个单链表析构函数是如何导致无限循环的?
【发布时间】:2019-05-15 12:51:41
【问题描述】:

我使用结构编写了一个单链表实现。它不是管理列表操作的外部类的一部分。相反,所有操作都直接由节点处理。

我知道,如果结构定义是类的一部分,比如 ListManager,则在 ListManager 实例上调用析构函数只需要遍历类管理的链表并删除每个节点。

然而,由于这个链表不是外部类的一部分,它自己管理所有的操作,我对如何编写析构函数有点困惑。

版本 1 运行良好,它是一个遍历列表并释放与每个节点关联的内存的递归调用。

版本 2 导致了无限循环。我不明白为什么,因为这是我为管理节点链表的容器类实现析构函数的一种方式。

版本 3 运行良好,但过于冗长。

我使用 valgrind 和 python 导师运行了所有三个版本来检查泄漏和其他问题。

任何帮助解释为什么版本 2 不起作用以及为什么以这种方式实现析构函数是不正确的,我们不胜感激!

结构链表

#include <iostream> 
#include <string> 
using namespace std; 

struct Node
{   
    int id; 
    Node* next; 
    Node(int newId = 0, Node* newNext = NULL)
    : id(newId), next(newNext) { } 
};

析构函数版本 1

~Node()
{
    if (next != NULL)
        delete next; 
}

析构函数第 2 版

~Node()
{
    Node* lead = this; 
    Node* follow = this;

    while (follow != NULL)
    {
        lead = lead->next; 
        delete follow; 
        follow = lead; 
    }
}

析构函数版本 3

~Node()
{
    Node* lead = this; 
    Node* follow = this;

    if (follow != NULL)
    {
        lead = lead->next; 
        delete follow; 
        follow = lead; 
    }
}

主要

int main()
{
    Node* head = NULL; 
    head = new Node(23, head); 
    head = new Node(54, head); 
    head = new Node(81, head);
    head = new Node(92, head); 
    delete head;     
    return 0; 
}

【问题讨论】:

  • 您通常应该有一个 LinkedList 类来管理它。 Node 的上下文太少,无法自行清除所有内容(它无法选择是清除所有内容还是只清除一个节点并重新链接列表的其余部分)。
  • 尝试1可以简化为delete next;,删除空指针即可
  • 将递归与循环结合通常不好。
  • 所有版本实际上都是递归的。第 1 版和第 3 版实际上是等价的——尽管第 3 版通过分配额外的变量来混淆它。在版本 2 中,delete follow 再次递归调用析构函数,该析构函数执行类似的循环,这也是递归的......因此它会尝试多次删除特定对象,这会导致未定义的行为。
  • @Peter no,版本 3 也是 delete this

标签: c++ linked-list destructor singly-linked-list


【解决方案1】:

在版本 2 中,您编写了一个循环,通过循环遍历列表并删除每个元素,在一次析构函数调用中清除整个列表。然而,发生的并不是你只有一个析构函数调用。每次删除元素时,都会再次调用析构函数。

所以最后,delete follow 在第一次调用时转换为delete this(因为follow = this;)。然后这会导致第一个节点的析构函数再次被调用,从而导致无限循环。

以下节点将被多次销毁,导致未定义的行为,但由于无限循环,它甚至无法到达那里。

【讨论】:

  • 这也是我的想法,但是当代码中唯一的区别是将while 替换为if 时,是什么让案例3 正常工作?
  • 未定义行为的最糟糕的症状是“似乎正常工作”
  • @Caleth 是对的。举个例子,如果我在 MSVC 中尝试第 3 版,它会因delete follow; 上的堆栈溢出而崩溃。
【解决方案2】:

您只需要每个Node 删除(最多)一个其他Node,最终删除所有节点。您重新分配本地指针不会影响列表的结构。

2 和 3 都是delete this,这在最好的情况下是可疑的,加上一些不相关的仪式,在析构函数中。它们都是未定义的行为,删除同一个对象(至少)两次。

您的第一次尝试已接近尾声。

不要在复制指针值时混淆自己,只需使用拥有的指针类型,例如std::unique_ptr

struct Node
{   
    int id; 
    std::unique_ptr<Node> next; 
    Node(int id = 0, std::unique_ptr<Node> next = {})
    : id(id), next(std::move(next)) { } 
    // correct destructor is implicit
};

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-02-16
    • 1970-01-01
    • 2013-10-07
    • 2016-05-01
    • 1970-01-01
    • 2021-06-15
    • 1970-01-01
    相关资源
    最近更新 更多