双链表无限循环

Doubly-linked list infinite loop?

本文关键字:无限循环 链表      更新时间:2023-10-16

如果我要创建一个节点类,如下所示,如果它用于双链表,它会在解构双链表时创建一个无限循环吗?还是会很好地结束?

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指针,节点正在形成一个循环,那么任何节点的破坏将确实可能形成一个无限递归循环,它将通过爆炸堆栈来终止程序。

可能发生的是

  1. 第一个"外部"delete node;发出
  2. 当进入节点析构函数时,还没有做任何事情,因为代码析构函数是销毁过程中执行的"第一件"事情(销毁过程本身非常复杂,按顺序包括析构函数代码、类更改、成员销毁、基销毁:参见此回答以获得更详细的解释)。
  3. 第一个析构指令填充执行delete mNext;,从而触发循环中下一个节点的相同进程。
  4. 因为节点正在形成一个循环,这个链将再次"从后面"到达node,从而使第一个调用成为一个永远不会结束的递归。
  5. 无论如何,每次调用都会为激活记录分配堆栈空间,因此一段时间后,允许用于堆栈的所有内存将被耗尽,操作系统将终止进程。删除调用不是一个"尾部调用",因为在析构函数代码完成后,内存必须被释放,所以这个递归不能轻易地被优化掉。虽然delete mNext;是析构函数上的最后一条语句,但在delete操作符完成之后,仍然有一些操作必须执行。
但是请注意,根据我的经验,除非使用特殊的编译器选项,否则不会检查堆栈溢出,因此程序终止将非常"不正常"。还要注意的是,在Windows下有一些可怕的代码,在某些情况下隐藏了段错误,如果它们发生在程序终止时,所以很有可能一个Windows程序在退出事件循环后可以很好地结束这个操作。

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

为什么我用这个词可能是?原因是c++标准规定对象的多次销毁是未定义行为。如果您将此添加到c++中没有完成析构函数就无法退出析构函数的事实中,您将理解理论上允许编译器将对象标记为"正在被销毁",并且如果两次进入同一对象的析构函数,则允许编译器让守护程序飞出您的鼻孔。然而,检查这个错误不是强制性的,编译器编写者通常很懒(这不是对程序员的侮辱),因此不太可能出现这个检查(除非启用了一些特殊的额外调试选项)。

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

任何事都可能发生。墨菲说,只要对你造成最大的伤害,就会发生这种情况……例如,当您开发程序时,程序每次都会很好地终止……当你在一千名潜在客户面前做演示时,它会在你脸上严重崩溃。

就是不要这样做:-)

它没有办法知道什么时候停止,所以它可能会无限地运行。
您可能应该编写一个List类,它具有指向(或实际的)Node的指针。Node的d'tor应该只处理它自己的字段,在本例中是mContactList的d'tor应该遍历列表中的所有节点(记住何时停止),并删除每个节点(正好一次)。

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

我不知道你在做什么"如果以前"选项。那些没用的。这将是一个有效的节点,因此有一个前一个节点,或者它将不是一个有效的节点,检查前一个将得到未定义的结果。坚持使用简单的答案:

class Node 
{ 
Node(  mNext = NULL; ); 
~Node(  ) 
{ 
   delete mNext; //deallocs next node 
} 
Contact mContact; 
Node* mPrevious; 
Node* mNext; 
};  

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

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

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

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


示例:LinkList为1->2->3->4->5->6->NULL对HeadNode的构造函数调用(参考第三个假设)将导致如下递归调用:删除(1)->删除(2)-> delete(3) ->删除(4)->删除(5)->删除(6)-> NULL所以请检查如果(mNext != NULL)删除mNext它工作了:)


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


最佳实践是使用STL库或使用自动指针类来处理问题的销毁部分