以下代码用于从排序列表中删除重复项。它在我的电脑上运行正常。我的问题是关于使用
head = head-> next;
之后
删除头;
下方。这是非法的吗?代码在我的编译器上生成正确的结果。它会依赖于编译器吗?或者它符合C ++ 11标准?
struct ListNode {
int val;
ListNode *next;
explicit ListNode(int x) : val(x), next(nullptr) { }
};
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if (head && (head->next = deleteDuplicates(head->next)) && head->next->val == head->val){
delete head;
head = head->next;
}
return head;
}
};
int main() {
ListNode *h = new ListNode(0);
auto cur=h;
cur->next= new ListNode(1);
cur=cur->next;
cur->next= new ListNode(2);
cur=cur->next;
cur->next= new ListNode(2);
cur=cur->next;
Solution sol;
auto xx=sol.deleteDuplicates(h);
return 0;
}
答案 0 :(得分:2)
不,它不是非法。当您尝试访问最近为delete
d并且未分配新值的指针时,它为Undefined Behaviour。请记住,UB 的结果可能一开始看起来是正确的,但是当它们突然“停止工作”时,它没有被指定(已定义)。
编辑:
由于有关于编译器是否应警告我们关于UB的问题,让我们来看看这个例子:
int main()
{
std::array<int, 5> arr = {1, 2, 3, 4, 5};
int position;
std::cin >> position;
std::cout << arr[position];
}
我们只是输入一个数字并在数组中输出该位置的值。看起来像一个简单的程序,但在position > 4
或position < 0
时会发生什么?然后我们访问无效索引(Array access out of bounds),导致未定义的行为。
编译器应该警告我们吗?也许,但想象出一个简单的代码可以产生多少警告。也许如果我们添加一些检查然后尝试访问该阵列,警告可能会消失,但请记住,这是一个非常简单的例子。并不总是编译器可以预测何时可能发生UB,因此它们通常不会警告我们。
答案 1 :(得分:1)
不,这不是“非法的”。根据C ++标准,未定义行为(取消引用delete
d指针),并且当行为未定义时,编译器根本不需要发出任何诊断。
当行为未定义时,结果可能随编译器而变化,选择编译器标志(例如优化设置),只有一些输入到程序而不是其他输入,或随着月亮的相位而变化(例如因为代码导致程序访问系统时钟。)
在简单的情况下,编译器可能能够检测到问题,并发出警告。大多数现代编译器都被配置为默认情况下不这样做(遗憾的是,作为程序员游说的历史结果,他们需要生成无需警告编译的代码,并且更关心该指标而不是关于他们的代码是否按要求工作)但可以被强制使用编译选项(例如警告标志)。
在复杂的情况下,编译器根本无法检测到未定义行为的实例。
许多未定义的行为是由边缘情况引起的 - 未定义的行为仅发生在非常特定的输入值集上,因此除非编译器评估程序在运行时可能收到的所有可能的输入集,否则无法检测到。
由于编译器优化而产生另一类未定义的行为 - 编译器执行大量代码转换(或代码的中间表示)以优化性能(或大小或某些其他度量)。到目前为止,还没有可以发出警告的编译器“由于优化器进行了100次不同的代码转换,发现在原始源的第20行和第40行之间可能存在一个或多个未定义行为的实例“。即使有一个编译器可以这样做,这样的消息也不会对程序员特别有用。