使用已删除的变量 - 它取决于编译器吗?

时间:2017-12-26 01:51:58

标签: c++ c++11

以下代码用于从排序列表中删除重复项。它在我的电脑上运行正常。我的问题是关于使用

  

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;
}

2 个答案:

答案 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 > 4position < 0时会发生什么?然后我们访问无效索引(Array access out of bounds),导致未定义的行为。

编译器应该警告我们吗?也许,但想象出一个简单的代码可以产生多少警告。也许如果我们添加一些检查然后尝试访问该阵列,警告可能会消失,但请记住,这是一个非常简单的例子。并不总是编译器可以预测何时可能发生UB,因此它们通常不会警告我们。

答案 1 :(得分:1)

不,这不是“非法的”。根据C ++标准,未定义行为(取消引用delete d指针),并且当行为未定义时,编译器根本不需要发出任何诊断。

当行为未定义时,结果可能随编译器而变化,选择编译器标志(例如优化设置),只有一些输入到程序而不是其他输入,或随着月亮的相位而变化(例如因为代码导致程序访问系统时钟。)

在简单的情况下,编译器可能能够检测到问题,并发出警告。大多数现代编译器都被配置为默认情况下不这样做(遗憾的是,作为程序员游说的历史结果,他们需要生成无需警告编译的代码,并且更关心该指标而不是关于他们的代码是否按要求工作)但可以被强制使用编译选项(例如警告标志)。

在复杂的情况下,编译器根本无法检测到未定义行为的实例。

许多未定义的行为是由边缘情况引起的 - 未定义的行为仅发生在非常特定的输入值集上,因此除非编译器评估程序在运行时可能收到的所有可能的输入集,否则无法检测到。

由于编译器优化而产生另一类未定义的行为 - 编译器执行大量代码转换(或代码的中间表示)以优化性能(或大小或某些其他度量)。到目前为止,还没有可以发出警告的编译器“由于优化器进行了100次不同的代码转换,发现在原始源的第20行和第40行之间可能存在一个或多个未定义行为的实例“。即使有一个编译器可以这样做,这样的消息也不会对程序员特别有用。