【问题标题】:Segmentation fault in traversing a LinkedList遍历 LinkedList 中的分段错误
【发布时间】:2016-01-16 01:35:22
【问题描述】:

我是 C++ 和一般指针的新手,但我似乎无法解释为什么我的两个几乎相同的代码之一会出现分段错误。我正在做一个简单的遍历LinkedList 的任务,这是我的第一种方法:

Node * curNode = head; //Head is the pointer to the head of the LinkedList
while(curNode) {
    curNode = curNode->next;
}

这会产生所需的结果并成功遍历整个 LinkedList。但是,这会引发Segmentation fault 错误:

Node * curNode = head;
while(curNode->next != NULL) {
    curNode = curNode->next;
}

将条件更改为强制转换的布尔值或!= nullptr 不会执行任何操作,因此条件不会在空指针上引发true。然而,我似乎无法弄清楚为什么我得到Segmentation fault,似乎适当的循环条件确保我不会访问任何空指针。

【问题讨论】:

  • 如果 head 是一个空指针,第二个代码 sn-p 会引入未定义的行为。
  • 你能把整个代码贴出来吗……因为我觉得这很好
  • 我在一个 head 不是 null 指针的测试用例上运行了这个
  • 如果headNULL,第二个版本显然会失败。在这种情况下,循环将在第一次迭代的顶部导致分段错误,因为它会尝试取消引用 NULL(具有较小的偏移量)。
  • 感谢大家的回答。我意识到出了什么问题,该站点在后台运行了三个测试用例,但只显示了这三个测试用例之一的std:input。分段错误确实来自一个隐藏的测试用例,其中输入为 NULL..... 抱歉

标签: c++ pointers linked-list


【解决方案1】:

我可以看到这引发段错误的唯一情况是head 指针本身是NULL

为确保head 不是NULL,以下应该起作用并防止段错误:

Node * curNode = head;
if (head) {
    while(curNode->next != NULL) {
        curNode = curNode->next;
    }
}

【讨论】:

    【解决方案2】:

    您应该检查curNode 是否也是NULL

    Node * curNode = head;
    while(curNode && curNode->next != NULL) {
        curNode = curNode->next;
    }
    

    【讨论】:

      【解决方案3】:

      嗯,它们并不相同:

      while(curNode->next != NULL) {
          curNode = curNode->next;
      }
      

      在第二个版本中,curNode 没有初始检查。

      所以,你也需要检查curNode

      while(curNode && curNode->next) {
          curNode = curNode->next;
      }
      

      或者从循环外的额外检查开始:

      if(curNode) // This will also check for the case where `head` would be `NULL`
          while(curNode->next) {
              curNode = curNode->next;
          }
      }
      

      但这会跳过列表中的最后一项。所以首选你的第一个版本:

      while(curNode) {
          curNode = curNode->next;
      }
      

      【讨论】:

        【解决方案4】:

        重写:

        Node * curNode = head;
        
        // presumably, head is initialized somehow in omitted code
        
        while(curNode->next != NULL) {
            curNode = curNode->next;
        }
        

        它有两个严重的问题。一个是相对微不足道的问题,它不考虑head 为空的情况。这会导致崩溃。

        第二个问题是即使第一个问题得到解决,它仍然算法不正确。它忽略了遍历列表的最后一个节点。

        如果curNode 是一个非空指针,但curNode->next 是空指针,这意味着“我们有一个有效的节点,它没有后继:curNode 是最后一个节点”。

        因此循环保护条件while (curNode->next != NULL) 表示“而curNode 不是最后一个节点”。它不等同于第一个循环,它遍历整个列表,包括最后一个节点。

        如果你想要遍历除最后一个节点之外的所有节点,那么它是正确的,我们可以理解为什么需要head 测试:如果head 为空,则列表为空,因此 它没有最后一个节点。由于它没有最后一个节点,因此不能将空列表划分为“最后一个节点”和“其他所有节点”。这就是为什么它是一个特殊情况,以及为什么额外的空检查只是为了特殊情况而存在。我们可以这样写:

        Node * curNode = head;
        
        if (head) {  // list is not empty: visit all but the last node
            while (curNode->next != NULL) {
                curNode = curNode->next;
            }
        } else { // special case: list has no last node
           // do nothing? Or maybe some special behavior.
        }
        

        如果我们对于特殊情况不需要任何特殊行为(什么都不做也没关系),那么我们可以这样写:

        while (head && curNode->next != NULL) { // fold the if test into the while
            curNode = curNode->next;
        }
        

        我们在循环的每次迭代中都在浪费地测试head;但是,编译器可以轻松地优化它,因为head 没有在循环体中被修改。这也是可能的:

        while (curNode && curNode->next != NULL) {
            curNode = curNode->next;
        }
        

        但是,它会在循环的每次迭代中浪费地测试 curNode。我们知道curNode 永远不能为空,除非在第一次迭代之前。在随后的迭代中,curNode 的值是从curNode->next 获得的,我们知道curNode->next 不为空,因为这是进入迭代的条件。也许您的编译器也可以解决这个问题,尽管它需要更深入的分析而不是优化对head 的访问。编译器必须遵循相同的数据流推理,即curNode 是从curNode->next 获得的,它不为空,但仅在第二次及后续迭代中才成立。

        【讨论】:

          【解决方案5】:

          你的第二个代码 sn-p 没问题,所以只有在 head 是 NULL 时才会出现 Segmentation fault 错误。

          【讨论】:

            猜你喜欢
            • 2017-10-14
            • 2013-08-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多