我是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
,似乎适当的循环条件确保我不访问任何空指针。
答案 0 :(得分:5)
我可以看到这个抛出段错误的唯一情况是head
指针本身是NULL
。
为确保head
不是NULL
,以下内容应该有效并防止出现段错误:
Node * curNode = head;
if (head) {
while(curNode->next != NULL) {
curNode = curNode->next;
}
}
答案 1 :(得分:1)
嗯,他们并不完全相同:
while(curNode->next != NULL) {
curNode = curNode->next;
}
第二版中curNode
没有初步检查。
因此,您还需要检查curNode
:
while(curNode && curNode->next) {
curNode = curNode->next;
}
或者从循环外的exta检查开始:
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;
}
答案 2 :(得分:1)
您应该检查curNode
是否NULL
:
Node * curNode = head;
while(curNode && curNode->next != NULL) {
curNode = curNode->next;
}
答案 3 :(得分:0)
您的第二个代码段很好,因此只有当头为Segmentation fault
时才会出现NULL
错误。
答案 4 :(得分:0)
重写:
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
不是null,因为这是进入迭代的条件。也许您的编译器也可以解决这个问题,尽管它需要更深入的分析,而不是优化对head
的访问。编译器必须遵循相同的数据流推理,curNode
是从curNode->next
获得的,它不是空的,但这只在第二次和后续迭代中都是正确的。