双向链表无限循环? [关闭]

Posted

技术标签:

【中文标题】双向链表无限循环? [关闭]【英文标题】:Doubly-linked list infinite loop? [closed] 【发布时间】:2011-11-24 12:37:32 【问题描述】:

如果我要创建一个节点类,如下所示,并且如果它用于双向链表,它会在解构双向链表时创建一个无限循环吗?或者它会很好地终止?

class Node

    Node(  );

    ~Node(  )
    
       delete mNext; //deallocs next node
    

    Contact mContact;
    Node* mPrevious;
    Node* mNext;
; 

编辑:如果我将代码修改为这样,它会工作吗?

~Node(  )

   mPrevious = NULL;
   if (mNext->mPrevious != NULL)
   
      delete mNext; //deallocs next node
   

编辑 2:或者这样效果最好?

~Node(  )

   if (mPrevious != NULL)
   
      mPrevious = NULL;
      delete mNext; //deallocs next node
   

【问题讨论】:

我不知道你为什么认为它会形成一个无限循环,你应该在删除它之前检查 mNext 是否不为 NULL。 由于没有设置 mNext,会发生崩溃。如果您确实在列表中添加了两次您不应该的内容,您也可以删除两次。也不要使用链接列表 当 STL 有大量列表结构可供选择时,我不确定为什么要编写自己的双向链表。 (除非这是家庭作业或其他形式的学习练习,也就是说……) @Nican:实际上,如果 mNext 为 NULL,您不应该检查。如果它为空,删除将忽略它。我最讨厌的事情之一是在删除之前检查代码是否为空 我只是想澄清一下——我们是在谈论一个循环双向链表,其中“第一个”和“最后一个”元素相互连接,还是只是一个以和开头的普通元素以NULL结尾? @acidzombie24 我不同意,我认为了解简单数据结构如何工作的基础知识很重要,尤其是链表 - 这稍后会让您直观了解其他数据结构以及不同操作的复杂性等。 【参考方案1】:

如果考虑到mNext 指针,节点正在形成一个循环,那么任何节点的破坏确实会可能形成一个无限递归循环,它会通过炸毁堆栈来终止程序.

可能发生的事情是

    发出第一个“外部”delete node;。 当进入节点析构函数时,还没有做任何事情,因为代码析构函数是销毁过程中执行的“第一”件事(销毁过程本身相当复杂,包括析构函数代码、类更改、成员销毁、基础销毁此顺序:请参阅this answer 以获得更详细的说明)。 第一个析构指令填充执行delete mNext;,从而在循环中的下一个节点上触发相同的进程。 因为节点正在形成一个循环,这条链将再次“从后面”到达node,从而使第一次调用成为永远不会结束的递归。 每次调用都会为激活记录分配堆栈空间,因此一段时间后所有允许用于堆栈的内存将被耗尽,操作系统将终止该进程。删除调用不是“尾调用”,因为在析构函数代码完成后,内存必须被释放,所以这个递归不能轻易被优化掉......而delete mNext;是析构函数的最后一条语句,仍然有一些操作必须是在delete 运算符完成后执行。

但是请注意,根据我的经验,除非您使用特殊的编译器选项,否则不会检查堆栈溢出,因此程序终止将非常“异常”。另请注意,在 Windows 下有一些可怕的代码,如果它们发生在程序终止时,在某些情况下会隐藏 segfault 错误,因此在退出事件循环后完成此操作时,Windows 程序很可能会优雅地终止。

考虑到堆栈溢出通常不被认为确实任何行为都是可能的,包括明显的“无限循环”(请注意,这个无限循环可能不是递归析构函数之一,而是运行时系统内部的某个地方变得疯狂,因为堆栈溢出)。

为什么我使用可能这个词?原因是 C++ 标准规定对象的多次破坏是未定义的行为。如果您将此添加到 C++ 中无法在不完成销毁的情况下退出析构函数的事实中,您将了解编译器理论上允许将对象标记为“正在销毁”并使守护程序飞出您的nosrils 如果你两次输入同一个对象的析构函数。但是检查这个错误不是强制性的,编译器编写者通常很懒(这不是对程序员的侮辱),因此不太可能出现这种检查(除非启用了一些特殊的额外调试选项)。

总结一下:它可以永远循环吗?是的。能崩溃吗?当然。它可以停止告诉我一个物体被摧毁了两次吗?当然。它可以很好地终止程序(即不设置任何错误代码)吗?是的,那也是。

任何事情都可能发生。墨菲说它会发生任何会对你造成最大伤害的事情......例如,当你开发它时,程序每次都会很好地终止......并且它会在你的演示日期间严重崩溃在一千名潜在客户面前。

别那样做 :-)

【讨论】:

【参考方案2】:

它无法知道何时停止,因此它可能会无限运行。 您可能应该编写一个List 类,它有一个指向(或实际的)Node 的指针。 Node 的 d'tor 应该只处理自己的字段,在这种情况下是 mContactList 的 d'tor 应该遍历列表中的所有节点(记住何时停止),并删除每个节点(只删除一次)。

【讨论】:

实际上它不会无限运行。如果 mNext 无效或为 null 时停止,则会崩溃。 @acidzombie24 但是首先假设该列表是合法的,则应在释放任何空间之前调用下一个节点的 d'tor。而且由于那个 d'tor 调用了下一个,等等,你会得到一个无限的运行。 hmmm dtor 订单的好点子。 +1:D @Eran,在某些时候 mNext 将为空,然后删除链将展开。我仍然不明白为什么它会是无限的。如果列表足够长,您可能会遇到堆栈问题,但不会遇到无穷大问题。 @SteveRowe 我的错,我认为它是一个循环双向链表,其中“最后一个”和“第一个”元素是相互连接的。【参考方案3】:

假设你将 mNext 初始化为 null,它不会无限运行。当遇到空指针时,Delete 什么也不做。因此它会在你期望的时候结束。

我不确定您使用“如果以前”选项做了什么。那些行不通。这将是一个有效的节点,因此有一个前一个节点,或者它不是一个有效的节点,检查前一个将有未定义的结果。坚持简单的答案:

class Node 
 
Node(  mNext = NULL; ); 

~Node(  ) 
 
   delete mNext; //deallocs next node 
 

Contact mContact; 
Node* mPrevious; 
Node* mNext; 
;  

澄清:此解决方案有效,但前提是满足两个条件: 1) 列表中没有节点出现两次。 2)列表不是循环的。 如果你能保证这些条件,这是你最简单的答案。如果不能,则需要做一些更复杂的事情。

【讨论】:

【参考方案4】:

我个人认为Node 的析构函数与其他节点有任何关系,这有点奇怪。

如果设计取决于我,我会创建一个 List 类,其中包含指向 Node 对象(firstlast)的指针。 List 类的析构函数将负责遍历列表中的所有节点并销毁它们。

【讨论】:

【参考方案5】:

这其实很简单。 假设 1)它是一个双向链接列表,而不是一个循环列表 2)链接列表中没有循环:这是一个双链接列表 3) 实现类只有一个 Node 实例,可能称为 HeadNode 或 LinkList ;) 这是显式销毁的节点


示例:LinkList 是 1->2->3->4->5->6->NULL HeadNode 的析构函数调用(参考第三个假设)将导致递归调用,如下所示: 删除(1)->删除(2)->删除(3)->删除(4)->删除(5)->删除(6)->NULL 所以请检查 if (mNext != NULL) delete mNext 它有效:)


但是:如果你想具体删除一个节点:假设我们想在上面的例子中只删除 4 个,所有的节点都将被删除直到 NULL,所以在删除之前请确保你将 Mnext 设置为 NULL。


最佳实践是使用 STL 库或以其他方式使用自动指针类来解决问题的一部分

【讨论】:

以上是关于双向链表无限循环? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

构造链表时的无限循环

尝试添加到链表时使用 Valgrind 进行无限“信号 11 被丢弃”循环

实体类双向映射进行Json序列化时出现无限循环的解决问题

js for循环不断循环无限[关闭]

C/C++ 中的无限循环 [关闭]

发出 Axios GET 请求时出现无限循环 - 循环不会关闭