c++ - 如何使用析构函数清除链表内存,而不会出现 valgrind 错误? [更新 : Operator Overload help]

标签 c++ debugging memory-management linked-list valgrind

我正在尝试通过 valgrind 调试我的代码,我看到了 invalid free() 问题。看来我的 frees 比我的 allocs 多。

valgrind 输出如下:

==11814== Memcheck, a memory error detector
==11814== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==11814== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==11814== Command: ./doublyLinkedList -v --leak-check=full
==11814== 
==11814== Invalid read of size 8
==11814==    at 0x400A7A: DLinkedList::~DLinkedList() (doublyLinkedList.cpp:92)
==11814==    by 0x400DD1: main (doublyLinkedList.cpp:175)
==11814==  Address 0x5a87c88 is 8 bytes inside a block of size 24 free'd
==11814==    at 0x4C2B1C6: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==11814==    by 0x400A90: DLinkedList::~DLinkedList() (doublyLinkedList.cpp:93)
==11814==    by 0x400DC5: main (doublyLinkedList.cpp:178)
==11814==  Block was alloc'd at
==11814==    at 0x4C2A0FC: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==11814==    by 0x4009B5: DLinkedList::DLinkedList(int) (doublyLinkedList.cpp:57)
==11814==    by 0x400D90: main (doublyLinkedList.cpp:175)
==11814== 
==11814== Invalid free() / delete / delete[] / realloc()
==11814==    at 0x4C2B1C6: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==11814==    by 0x400A90: DLinkedList::~DLinkedList() (doublyLinkedList.cpp:93)
==11814==    by 0x400DD1: main (doublyLinkedList.cpp:175)
==11814==  Address 0x5a87c80 is 0 bytes inside a block of size 24 free'd
==11814==    at 0x4C2B1C6: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==11814==    by 0x400A90: DLinkedList::~DLinkedList() (doublyLinkedList.cpp:93)
==11814==    by 0x400DC5: main (doublyLinkedList.cpp:178)
==11814==  Block was alloc'd at
==11814==    at 0x4C2A0FC: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==11814==    by 0x4009B5: DLinkedList::DLinkedList(int) (doublyLinkedList.cpp:57)
==11814==    by 0x400D90: main (doublyLinkedList.cpp:175)
==11814== 
==11814== 
==11814== HEAP SUMMARY:
==11814==     in use at exit: 72,704 bytes in 1 blocks
==11814==   total heap usage: 3 allocs, 4 frees, 72,752 bytes allocated
==11814== 
==11814== LEAK SUMMARY:
==11814==    definitely lost: 0 bytes in 0 blocks
==11814==    indirectly lost: 0 bytes in 0 blocks
==11814==      possibly lost: 0 bytes in 0 blocks
==11814==    still reachable: 72,704 bytes in 1 blocks
==11814==         suppressed: 0 bytes in 0 blocks
==11814== Rerun with --leak-check=full to see details of leaked memory
==11814== 
==11814== For counts of detected and suppressed errors, rerun with: -v
==11814== ERROR SUMMARY: 4 errors from 2 contexts (suppressed: 0 from 0)

供引用,我的双向链表代码如下: (我知道我没有释放反向指针,但即使在我开始之前,我似乎也无法释放单向指针。)

#include <iostream>

class Node {
  friend class DLinkedList;
private:
  Node *_pPrev;
  Node *_pNext;
  int _data;

public:
  Node(): _pPrev(nullptr), _pNext(nullptr) { }
  Node(int d): _data(d), _pPrev(nullptr), _pNext(nullptr) { }
  Node(int d, Node *p, Node *n): _data(d), _pPrev(p), _pNext(n) { }

  // Getters
  const int getData() {
    return _data;
  }

  const Node* getPreviousNode() {
    return _pPrev;
  }

  const Node* getNextNode() {
    return _pNext;
  }
};

class DLinkedList {
private:
  Node *_pHead;
  Node *_pTail;

public:
  DLinkedList();
  DLinkedList(int d);
  DLinkedList(Node *n);
  DLinkedList(const DLinkedList &dLList); // Copy Constructor
  ~DLinkedList();  // Destructor

  const DLinkedList operator+(const DLinkedList &dLList) const;
  DLinkedList& operator=(const DLinkedList &dLList); // Assignment operator overload

  void listDisplay();
  void reverseListDisplay();
  void append(int d);
  void append(Node *n);
  void append(const DLinkedList &dLList);
};

DLinkedList::DLinkedList() {
  _pHead = _pTail = nullptr;
}

DLinkedList::DLinkedList(int d) {
  _pHead = new Node(d, nullptr, nullptr);
  _pTail = _pHead;
}

DLinkedList::DLinkedList(Node *n) {
  _pHead = n;
  _pTail = _pHead;
}

DLinkedList::DLinkedList(const DLinkedList &dLList) {
  _pHead = dLList._pHead;
  _pTail = dLList._pTail;
}

DLinkedList& DLinkedList::operator=(const DLinkedList &dLList) {
  return *this;
}

DLinkedList::~DLinkedList() {
  while (Node *currentHead = _pHead) {
    Node *next = currentHead->_pNext;
    _pHead = currentHead->_pNext;
    delete currentHead;
  }
}

const DLinkedList DLinkedList::operator+(const DLinkedList &dLList) const {
  DLinkedList temp(*this);

  temp._pTail->_pNext = dLList._pHead;
  temp._pTail->_pNext->_pPrev = temp._pTail;
  temp._pTail = dLList._pTail;

  return temp;
}

void DLinkedList::listDisplay() {
  if (_pHead == nullptr) {
    std::cout << "List is empty!" << std::endl;
    return;
  }

  Node *it = _pHead;
  while (it != nullptr) {
    std::cout << it->_data << std::endl;
    it = it->_pNext;
  }
  std::cout << std::endl;
}

void DLinkedList::reverseListDisplay() {
  if (_pHead == nullptr) {
    std::cout << "List is empty!" << std::endl;
    return;
  }

  Node *it = _pTail;
  while (it != nullptr) {
    std::cout << it->_data << std::endl;
    it = it->_pPrev;
  }
  std::cout << std::endl;
}

void DLinkedList::append(int d) {
  if (_pHead == nullptr) {
    _pHead = new Node(d, nullptr, nullptr);
    _pTail = _pHead;
    return;
  }

  Node *n = new Node(d, _pTail, nullptr);
  _pTail->_pNext = n;
  _pTail = _pTail->_pNext;
}

void DLinkedList::append(Node *n) {
  if (_pHead == nullptr) {
    _pHead = n;
    _pTail = _pHead;
    return;
  }

  _pTail->_pNext = n;
  _pTail->_pNext->_pPrev = _pTail;
  _pTail = _pTail->_pNext;
}

void DLinkedList::append(const DLinkedList &dLList) {
  _pTail->_pNext = dLList._pHead;
  _pTail->_pNext->_pPrev = _pTail;
  _pTail = dLList._pTail;
}

int main() {
  DLinkedList listA(10);
  listA.append(20);

  DLinkedList listB(listA);
}

我正在关注 Rule of Three .有人可以指出正确的方向来理解为什么我会看到这个吗?我已经研究并尝试了许多不同的实现,但有些只会破坏它更糟。具体来说,当在 main() 中调用 DLinkedList listB(listA); 时,问题似乎出现了。

更新: 感谢你们的帮助,我能够找出问题所在。但是现在,在扩展中,我遇到了与运算符重载类似的问题。谢谢你帮我。希望在正确的方向上得到一些“指示”。 Valgrind 和代码如下:

==30270== Memcheck, a memory error detector
==30270== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==30270== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==30270== Command: ./doublyLinkedList
==30270== 
10
20
30

30
20
10

10
20
30
10
20
30

==30270== Invalid read of size 8
==30270==    at 0x400C31: DLinkedList::~DLinkedList() (doublyLinkedList.cpp:119)
==30270==    by 0x400F83: main (doublyLinkedList.cpp:189)
==30270==  Address 0x5a87da8 is 8 bytes inside a block of size 24 free'd
==30270==    at 0x4C2B1C6: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==30270==    by 0x400C47: DLinkedList::~DLinkedList() (doublyLinkedList.cpp:120)
==30270==    by 0x400F77: main (doublyLinkedList.cpp:193)
==30270==  Block was alloc'd at
==30270==    at 0x4C2A0FC: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==30270==    by 0x400A57: DLinkedList::DLinkedList(DLinkedList const&) (doublyLinkedList.cpp:73)
==30270==    by 0x400F2B: main (doublyLinkedList.cpp:189)
==30270== 
==30270== Invalid free() / delete / delete[] / realloc()
==30270==    at 0x4C2B1C6: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==30270==    by 0x400C47: DLinkedList::~DLinkedList() (doublyLinkedList.cpp:120)
==30270==    by 0x400F83: main (doublyLinkedList.cpp:189)
==30270==  Address 0x5a87da0 is 0 bytes inside a block of size 24 free'd
==30270==    at 0x4C2B1C6: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==30270==    by 0x400C47: DLinkedList::~DLinkedList() (doublyLinkedList.cpp:120)
==30270==    by 0x400F77: main (doublyLinkedList.cpp:193)
==30270==  Block was alloc'd at
==30270==    at 0x4C2A0FC: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==30270==    by 0x400A57: DLinkedList::DLinkedList(DLinkedList const&) (doublyLinkedList.cpp:73)
==30270==    by 0x400F2B: main (doublyLinkedList.cpp:189)
==30270== 
==30270== 
==30270== HEAP SUMMARY:
==30270==     in use at exit: 72,704 bytes in 1 blocks
==30270==   total heap usage: 11 allocs, 13 frees, 73,944 bytes allocated
==30270== 
==30270== LEAK SUMMARY:
==30270==    definitely lost: 0 bytes in 0 blocks
==30270==    indirectly lost: 0 bytes in 0 blocks
==30270==      possibly lost: 0 bytes in 0 blocks
==30270==    still reachable: 72,704 bytes in 1 blocks
==30270==         suppressed: 0 bytes in 0 blocks
==30270== Rerun with --leak-check=full to see details of leaked memory
==30270== 
==30270== For counts of detected and suppressed errors, rerun with: -v
==30270== ERROR SUMMARY: 6 errors from 2 contexts (suppressed: 0 from 0)

仅发布运算符重载、复制构造函数和析构函数:

DLinkedList::DLinkedList(const DLinkedList &dLList){

  Node *n1 = nullptr; // Current
  Node *n2 = nullptr; // Next

  if (dLList.pHead_ == nullptr) {
    pHead_ = nullptr;
  } else {
    pHead_ = new Node();
    pHead_->data_ = dLList.pHead_->data_;

    n1 = pHead_;
    n2 = dLList.pHead_->pNext_;
  }

  while (n2) {
    Node *prev = n1;
    n1->pNext_ = new Node();
    n1 = n1->pNext_;
    n1->pPrev_ = prev;
    n1->data_ = n2->data_;

    n2 = n2->pNext_;
  }

  pTail_ = n1;
  n1->pNext_ = nullptr;
}

DLinkedList& DLinkedList::operator=(const DLinkedList &dLList) {
  DLinkedList temp(dLList);
  std::swap(temp.pHead_, pHead_);
  return *this;
}

DLinkedList& DLinkedList::operator+=(const DLinkedList &dLList) {
  (*this).pTail_->pNext_ = dLList.pHead_;
  (*this).pTail_->pNext_->pPrev_ = (*this).pTail_;
  (*this).pTail_ = dLList.pTail_;
  return *this;
}

const DLinkedList DLinkedList::operator+(const DLinkedList &dLList) const {
  DLinkedList temp = *this;

  temp += dLList;
  return temp;
}

DLinkedList::~DLinkedList() {

  Node *currentHead = pHead_;
  Node *currentTail = pTail_;
  while (Node *currentHead = pHead_) {
    pHead_ = currentHead->pNext_;
    delete currentHead;
  }
  pHead_ = nullptr;
  pTail_ = nullptr;
}

最佳答案

您正在“遵循”三则规则,但您根本不了解规则的内容:

DLinkedList::DLinkedList(const DLinkedList &dLList) {
    _pHead = dLList._pHead;
    _pTail = dLList._pTail;
}

此实现是生成的默认构造函数将执行的操作。您需要实际复制 元素。实际上,您只需复制指针,拷贝和原始指针的析构函数将删除相同的元素。

关于c++ - 如何使用析构函数清除链表内存,而不会出现 valgrind 错误? [更新 : Operator Overload help],我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39194948/

相关文章:

c++ - dlopen 在 gdb 中运行时导致段错误

具有特殊情况的 Python 规范化路径名

windows - Windows 崩溃对话框中十六进制数字的含义

c++ - 编译 Qt 应用程序以获得更好的调试信息 (Linux)

javascript - 在javascript函数和内存管理中声明vars

c++ - DSA 公钥大于私钥

c++ - 如何将参数传递给从 CPP 中的静态库加载的方法

c# - 画一个圆的边界?

objective-c - 圆弧 : __bridge versus __bridge_retained using contextInfo test case

C#:对象的内存使用情况