【问题标题】:C++ memory leak in linked-list push_front and push_back [closed]链表 push_front 和 push_back 中的 C++ 内存泄漏 [关闭]
【发布时间】:2018-12-19 19:24:03
【问题描述】:

valgrind 收到此内存泄漏错误:

 24 bytes in 1 blocks are definitely lost in loss record 1 of 11        
 at 0x4C2C21F: operator new(unsigned long) (vg_replace_malloc.c:334)
   by 0x413E47: pjc::list::push_back(double) (list.cpp:33)
   by 0x416371: ____C_A_T_C_H____T_E_S_T____4() (tests-list-01.cpp:86)

24 bytes in 1 blocks are definitely lost in loss record 2 of 11
   at 0x4C2C21F: operator new(unsigned long) (vg_replace_malloc.c:334)
   by 0x414047: pjc::list::push_front(double) (list.cpp:66)
   by 0x4192C1: ____C_A_T_C_H____T_E_S_T____10() (tests-list-01.cpp:146)

我的.hpp 链表文件如下所示:

using std::size_t;

namespace pjc {

class list {
private:
    struct node {
        double val = 0;
        node* prev = nullptr;
        node* next = nullptr;
    };

    node* head = nullptr;
    node* tail = nullptr;
    size_t num_elements = 0;

public:
    list() = default;
    list(const list& rhs);
    list& operator=(const list& rhs);
    list(list&& rhs);
    list& operator=(list&& rhs);
    ~list();

    void push_back(double elem);

    void push_front(double elem); 
};

push_back()push_front() 和链表的析构函数的定义如下:

list::list(const list &rhs) {
    head = tail = nullptr;
    for(node* tmp = rhs.head; tmp!=NULL; tmp=tmp->next) {
        push_back(tmp->val);
    }
    num_elements = rhs.num_elements;
}

list::~list() {
    node *T = head;
    while(T != nullptr)
    {
        node *T2 = T;
        T = T->next;
        delete T2;
    }
    head = nullptr;
    tail = nullptr;
    num_elements = 0;
}

void list::push_back(double elem) {
    node *n = new node;
    n->val = elem;
    if(tail == nullptr)
    {
        head = n;
        tail = head;
    }
    else
    {
        tail->next = n;
        n->prev = tail;
        tail = n;
    }
    num_elements++;
}

void list::push_front(double elem) {
    node *n = new node;
    n->val = elem;
    if(head == nullptr)
    {
        head = n;
        tail = head;
    }
    else
    {
        head->prev = n;
        n->next = head;
        head = n;
    }
    num_elements++;
}
list &list::operator=(const list &rhs) {
    list temp(rhs);
    std::swap(head, temp.head);
    std::swap(tail, temp.tail);
    std::swap(num_elements, temp.num_elements);
    return *this;
}

list::list(list &&rhs) {
    head = rhs.head;
    tail = rhs.tail;
    num_elements = rhs.num_elements;

    rhs.head = nullptr;
    rhs.tail = nullptr;
    rhs.num_elements = 0;
}

list &list::operator=(list &&rhs) {
    this->~list(); // Destroy our current contents
    std::swap(head, rhs.head);
    std::swap(tail, rhs.tail);
    std::swap(num_elements, rhs.num_elements);
    return *this;
}

我尝试更改析构函数,但似乎没问题。我真的不知道泄漏发生在哪里。

编辑:对不起,我第一次遗漏了代码的一些重要部分。现在它应该遵循 5 的规则。

【问题讨论】:

  • 引人注目的是违反了三法则。
  • IOW,您缺少复制构造函数和复制赋值运算符。而事实上,这段代码显然是使用 C++11 或更高版本的,所以你需要遵循五规则,其中包括添加移动构造函数和移动赋值运算符。
  • 也许使用智能指针而不是原始指针?或者使用 std::list 而不是实现自己的双向链表?
  • 掌握链表(或任何其他指针驱动的结构)的最佳方法之一是绘制图片。画出您希望列表看起来的样子。如果你发现自己画的东西不是你写的,那就是一个错误。然后按照您的代码,并尝试绘制相同的列表。如果你不能,那是一个错误,你可能知道它在哪里。
  • @Slava "现在看看你的代码 - 你做了所有 3 件事吗?不,你没有" - 实际上,确实如此。

标签: c++ memory-leaks linked-list valgrind doubly-linked-list


【解决方案1】:

我在您显示的代码中看不到任何泄漏,但您没有显示所有相关代码。

例如,您如何使用list 对象可能是导致泄漏的原因。例如,如果您没有通过实现正确的复制和移动构造函数以及复制和移动赋值运算符来遵循Rule of 3/5/0,那么在复制/移动list 对象时可能会泄漏内存。但是您没有显示该代码,因此我们无法判断您的操作是否正确。

话虽如此,您的析构函数确实有一个额外的 delete 不属于,您的 push_back()push_front() 方法可以简化。

最安全的选择是简单地使用std::list 并让它为您管理内存。但是,如果您想手动操作,请尝试以下操作:

class list
{
private:
    struct node
    {
        double val;
        node* prev = nullptr;
        node* next = nullptr;

        node(double value = 0) : val(value) {}
    };

    node* head = nullptr;
    node* tail = nullptr;
    size_t num_elements = 0;

public:
    list() = default;
    list(const list &src);
    list(list &&src);
    ~list();

    list& operator=(const list &rhs);
    list& operator=(list &&rhs);

    void push_back(double elem);
    void push_front(double elem);

    void swap(list &other)
};

list::list(const list &src)
    : list()
{
    for(node *n = src.head; n != nullptr; n = n->next)
        push_back(n->val);
}

list::list(list &&src)
    : list()
{
    src.swap(*this);
}

list::~list()
{
    node *n = head;
    while (n)
    {
        node *next = n->next;
        delete n;
        n = next;
    }
}

list& list::operator=(const list &rhs)
{
    if (this != &rhs)
        list(rhs).swap(*this);
    return *this;
}

list& operator=(list &&rhs)
{
    list(std::move(rhs)).swap(*this);
    return *this;
}

void list::push_back(double elem)
{
    node *n = new node(elem);
    if (tail)
    {
        tail->next = n;
        n->prev = tail;
    }
    else
        head = n;
    tail = n;
    ++num_elements;
}

void list::push_front(double elem)
{
    node *n = new node(elem);
    if (head)
    {
        head->prev = n;
        n->next = head;
    }
    else
        tail = n;
    head = n;
    ++num_elements;
}

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

【讨论】:

  • @WhozCraig 完成
【解决方案2】:

也许这会对你有所帮助:

template<typename T>
class List {
private:
    Node<T>* head = nullptr;
    Node<T>* tail = nullptr;

    std::size_t _size = 0;

public:

    List() = default;

    // copy constructor
    List(const List<T>& l) {
        _size = l._size;
        Node<T>* current = nullptr;
        Node<T>* previous = nullptr;
        for (std::size_t i = 0; i < l._size; ++i) {
            current = new Node<T>(l[i].data);
            current->prev = previous;
            if (previous) {
                previous->next = current;
            } else {
                head = current;
            }
            previous = current;
        }
        tail = current;
    }

    // assignment operator
    List<T>& operator=(const List<T>& l) {
        if (l.isEmpty()) {
            this->clear();
            return *this;
        }

        // keeps existing nodes intact, and only changes their value
        while (_size > l.size()) {
            Node<T>* prev = tail->prev;
            delete tail;
            prev->next = nullptr;
            tail = prev;
            --_size;
        }

        Node<T>* temp = head;
        Node<T>* tempL = l.head;
        for (std::size_t i = 0; i < _size; ++i) {
            temp->data = tempL->data;
            temp = temp->next;
            tempL = tempL->next;
        }

        while (_size < l._size) {
            this->append(tempL->data);
            tempL = tempL->next;
            ++_size;
        }

        return *this;
    }

    ~List() {
        Node<T>* temp = head;
        while (temp) {
            Node<T>* next = temp->next;
            delete temp;
            temp = next;
        }
    }

    void append(const T& value) {
        auto* temp = new Node<T>(value);
        if (!head) {
            // no head also means no tail
            head = temp;
            tail = temp;
        } else {
            tail->next = temp;
            temp->prev = tail;
            tail = temp;
        }
        ++_size;
    }

    void prepend(const T& value) {
        auto* temp = new Node<T>(value);
        temp->next = head;
        if (head) {
            head->prev = temp;
        }
        head = temp;
        ++_size;
    }
};

也就是说,您可能应该遵循三法则并实现复制构造函数和赋值运算符。

【讨论】:

  • 由于这段代码使用的是 C++11 或更高版本,它应该真正遵循五规则。
猜你喜欢
  • 1970-01-01
  • 2013-04-18
  • 2013-03-01
  • 2011-06-28
  • 1970-01-01
  • 1970-01-01
  • 2012-12-13
  • 2011-05-15
  • 2013-03-08
相关资源
最近更新 更多