【问题标题】:write access violation this->head was 0xDDDDDDDD in pop_front写访问冲突 this->head 在 pop_front 中为 0xDDDDDDDD
【发布时间】:2020-02-11 12:21:10
【问题描述】:

我有一些链表代码,一切正常,直到我在析构函数中创建 pop_front 函数。该代码在其他任何地方都可以使用,我尝试打印链接列表以查看它是否正确创建并且确实如此。这是我的代码中唯一调用 pop_front 的部分,也是我释放任何东西的唯一部分

template <typename T>
class DList {
    Node<T>* head;
    Node<T>* tail;
public:
    DList() {
        head = nullptr;
        tail = nullptr;
    }

    void push_front(T newData) {
        Node<T>* newNode = new Node<T>(newData, head, nullptr);
        if (head) {
            head->prev = newNode;
        }
        else {
            tail = newNode;
        };
        head = newNode;
    }
    void push_front(DList<T> newList) {

        newList.tail->next = head;
        head->prev = newList.tail;
        head = newList.head;

    }
    void pop_front() {
        if (head) {
            Node<T>* pop = head;
            head = pop->next;
            if (head) {
                head->prev = nullptr;
            }
            else {
                tail = nullptr;
            }
            delete pop;
        }
    }
    ~DList() {
        while (head) {
            pop_front();
        }
    }
};

【问题讨论】:

  • 您没有显示Node 的定义,或者它自己的析构函数的作用。但是我看到您的 push_front(DList&lt;T&gt;) 方法根本没有考虑空列表,更重要的是,它在将节点链接到目标列表后不会释放其节点的所有权,因此您最终会得到源列表和目标列表尝试在它们都被破坏时释放同一组节点。
  • 在某些编译器的调试模式下,释放的内存会被字节 DD 覆盖。如果你看到这个地址,那么你几乎肯定是在读取已经在其他地方释放的内存(释放后使用)。您的班级可能没有关注rule of three/five。事实上,尝试复制 DList 导致释放后使用,因为您没有对列表进行值复制。
  • 当您看到像 0xDDDDDDDD 这样高度重复的数字时,很有可能计算机正试图告诉您一些事情。在Magic Debug Numbers 的列表中查找它。如果您得到一个看起来很奇怪的十进制数,请将其转换为十六进制,然后查找它。如果你得到的数字大多是重复的,但在最不重要的一端有噪音,你可能有一个添加了偏移量的幻数。假设模式继续并进行查找。

标签: c++ memory-management linked-list


【解决方案1】:

您的DList 类(也可能还有模板类Node,我们看不到它的定义)不遵循rule of three/five。您定义了析构函数,但没有定义复制构造函数或复制赋值运算符。

这意味着复制DList(几乎肯定会调用DList::push_front(DList&lt;T&gt;))将导致析构函数中的use-after-free。

请记住,编译器生成的复制构造函数和复制赋值运算符只是执行值的成员复制。在这种情况下,您的值是指针。因此,DList 对象的副本将简单地将原始指针复制到新对象。

当其中一个对象被破坏时,列表将被拆除,但是另一个对象仍将持有指向已释放内存分配的指针。

如果您希望您的类型是可复制的,您必须实现复制构造函数和复制赋值运算符,因为编译器生成的版本会做错事。如果您使用的是 C++11,您还应该定义移动构造函数和移动赋值运算符。

在 C++11 中,您还可以删除这些构造函数和运算符,然后在尝试复制的任何地方都会收到编译时错误:

DList(DList const &) = delete;
DList(DList &&) = delete;

DList & operator=(DList const &) = delete;
DList & operator=(DList &&) = delete;

如果没有Node 模板类的定义,我无法建议复制构造函数或复制赋值运算符的实现。但是,我可以建议移动变体的实现:

void swap(DList & other) {
    std::swap(head, other.head);
    std::swap(tail, other.tail);
}

DList(DList && other) : head(nullptr), tail(nullptr) {
    swap(other);
}

DList & operator=(DList && other) {
    swap(other);
    return *this;
}

另一个错误:

void push_front(DList<T> newList)

应该是:

void push_front(DList<T> & newList)

因为您修改了“新列表”——修改副本没有多大意义。但是这个的语义和push_front(T)不匹配!

单值重载接受一个值并将其推送到 this 列表中。

列表重载采用另一个列表并将 this 列表推入 other 列表。列表重载将 other 列表推送到 this 列表会更有意义,因为它会反映单值重载的行为(“将这个东西添加到这个列表,不管是什么意思”)。

这个方法的实现也有缺陷,因为它没有将任何一个列表的指针设置为 null,这意味着你正在设置另一个 use-after-free。

【讨论】:

  • 我在我的主程序中创建了一个链表数组,如果这个错误是由于我释放了我已经释放的东西而导致的,我该如何避免这个数组呢?同一个链表会出现在多个索引处,所以如果我想释放数组,我是否不必遍历数组并分别释放每个链表,那么当我遇到一个时不会出现同样的问题我'即使使用复制构造函数和赋值运算符,也已经释放了?
  • @Exalino 没有正确编写的复制构造函数和复制赋值运算符。您遇到的问题是 多个 DList 实例认为 它们拥有相同的指针。 复制 DList 实例将涉及 复制 指向的-到数据结构到 new 分配,以便两个列表包含 相等 值序列,但指向 不同 内存分配。那么当一个列表被销毁时,它只是释放它自己的自己的数据。如果你贴出Node模板类的定义,我或许可以为你写一个示例实现。
猜你喜欢
  • 2020-06-17
  • 1970-01-01
  • 2020-11-15
  • 2017-01-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-09
  • 2020-02-13
相关资源
最近更新 更多