【问题标题】:Advice on resolving memory leak with "new" keyword关于使用“new”关键字解决内存泄漏的建议
【发布时间】:2021-05-04 17:47:39
【问题描述】:

我有一个关于如何解决我的 c++ 类中分配的内存泄漏的问题。在这个任务中,我应该实现一个 BST。我已经这样做了,而且功能方面,一切都按我的预期工作。但是,我的程序中有内存泄漏。唯一的问题是,当我尝试使用 Valgrind 调试我的内存泄漏时,我被指向我编译的代码中我没有编写的一行。在下面的函数中,我没有写这个函数,这是我的教授给我的,Valgrind指向下面函数中的NodeData* ptr = new NodeData(s);行:

void buildTree(BinTree& T, ifstream& infile) {
    string s;

    for (;;) {
        infile >> s;
        cout << s << ' ';
        if (s == "$$") break;                // at end of one line
        if (infile.eof()) break;             // no more lines of data
        NodeData* ptr = new NodeData(s);     // NodeData constructor takes string
        // would do a setData if there were more than a string

        bool success = T.insert(ptr);
        if (!success)
            delete ptr;                       // duplicate case, not inserted 
    }
}

在我的教授创建的 BST 驱动程序文件中,他正在分配 NodeData 指针。 NodeData 类也是教授给我的,但问题(我认为)是 NodeData 类的析构函数是空的。这是 NodeData 类的 .cpp 文件:

#include "nodedata.h"

//------------------- constructors/destructor  -------------------------------
NodeData::NodeData() { data = ""; }                         // default

NodeData::~NodeData() { }            // needed so strings are deleted properly

NodeData::NodeData(const NodeData& nd) { data = nd.data; }  // copy

NodeData::NodeData(const string& s) { data = s; }    // cast string to NodeData

//------------------------- operator= ----------------------------------------
NodeData& NodeData::operator=(const NodeData& rhs) {
    if (this != &rhs) {
        data = rhs.data;
    }
    return *this;
}

//------------------------- operator==,!= ------------------------------------
bool NodeData::operator==(const NodeData& rhs) const {
    return data == rhs.data;
}

bool NodeData::operator!=(const NodeData& rhs) const {
    return data != rhs.data;
}

//------------------------ operator<,>,<=,>= ---------------------------------
bool NodeData::operator<(const NodeData& rhs) const {
    return data < rhs.data;
}

bool NodeData::operator>(const NodeData& rhs) const {
    return data > rhs.data;
}

bool NodeData::operator<=(const NodeData& rhs) const {
    return data <= rhs.data;
}

bool NodeData::operator>=(const NodeData& rhs) const {
    return data >= rhs.data;
}

//------------------------------ setData -------------------------------------
// returns true if the data is set, false when bad data, i.e., is eof

bool NodeData::setData(istream& infile) {
    getline(infile, data);
    return !infile.eof();       // eof function is true when eof char is read
}

//-------------------------- operator<< --------------------------------------
ostream& operator<<(ostream& output, const NodeData& nd) {
    output << nd.data;
    return output;
}

它很短,但是你可以看到析构函数是空的。我不知道是否允许我编辑 NodeData 的析构函数来解决此内存泄漏问题,并且我不确定我的代码中的其他地方如何/在哪里可以删除这些指针(因为我也无法编辑驱动程序文件本身)。这不是我以前处理过的事情,也是我需要帮助的事情。有人可以帮我解决这个问题,并就我可以调查如何解决此内存泄漏的方法给我建议吗?

  1. 是否可以解决由于不删除指针所创建的指针导致的内存泄漏:NodeData* ptr = new NodeData(s); 在指针被传递到的函数中(插入函数。如果需要,我可以提供此代码)。
  2. 如果无法解决插入函数内部的内存泄漏,还有其他地方可以解决吗?我想我可能能够编辑 NodeData 类/制作析构函数,但我不完全确定如何在 NodeData 类中实现这样的析构函数。

由于它可能会有所帮助,这里是由 buildTree 函数引用的 BinTree 类的插入函数:

bool BinTree::insert(NodeData* nodeData){
    if(root != nullptr){ //if the root of the tree is not NULL, meaning that the BST object exists, we call our insertHelp function.
        insertHelp(this->root, nodeData);
        return true;
    }
    else{ //If there is no root node created in our tree, we create a root node.
        root = new Node;
        root->left = nullptr;
        root->right = nullptr;
        root->data = nodeData;
        return true;
    }
    return true;
}
// ---------------------------------insertHelp--------------------------------------------------
// Description: Private function that recursively calls itself in order to find the correct location in a BinTree object to add a new node.
// ---------------------------------------------------------------------------------------------------
bool BinTree::insertHelp(Node *nodePointer, NodeData* nodeData){
      if(*nodeData < *nodePointer->data){ //Here we are deciding if we need to traverse left or not.
        if(nodePointer->left != nullptr){ //If there is a node to the left, we call insertHelper again but on this node to the left.
            insertHelp(nodePointer->left, nodeData);
        }
        else{ //if there is no node the left, and we need to go left, we create a new left node.
            nodePointer->left = new Node;
            nodePointer->left->left = nullptr;
            nodePointer->left->right = nullptr;
            nodePointer->left->data = nodeData;
            return true;
        }   
    } 
    else if(*nodeData > *nodePointer->data){//Here we are deciding if we need to traverse right or not.
        if(nodePointer->right != nullptr){ //if there is no node the right, and we need to go left, we create a new left node.
            insertHelp(nodePointer->right, nodeData);
        }
        else{ //if there is no node the right, and we need to go right, we create a new right node.
            nodePointer->right = new Node;
            nodePointer->right->left = nullptr;
            nodePointer->right->right = nullptr;
            nodePointer->right->data = nodeData;
            return true;
        }
    }
    else{ //catch all case. If something goes wrong, return false.
        return false;
    }
    return true;
}

这是 BinTree 的当前析构函数:

BinTree::~BinTree(){
    makeEmpty();
}   
void BinTree::makeEmpty(){
    //You make a tree empty by deleting all notes in a post-order traversal.
    postOrderDeleteNode(this->root); //I will call my private postOrderDelete helper function. I broke things up this way to make the code cleaner.
}

// ---------------------------------postOrderDeleteNode--------------------------------------------------
// Description: This is the private function that performs a post-order traversal of the nodes in a BinTree
//              and deletes each node by deallocating the memory assigned when each node is created.
// ---------------------------------------------------------------------------------------------------
void BinTree::postOrderDeleteNode(const Node *rootNode){
    if(rootNode == nullptr){ //Base case, we have an empty tree at this node
        return;
    }
    else{
        postOrderDeleteNode(rootNode->left); //First we delete the left side of the tree
        postOrderDeleteNode(rootNode->right); //Then we delete the right side of the tree.
        delete rootNode; //We finally delete the root of the entire BST.
        this->root = nullptr; 
    }
}

这里是 BinTree 的 .h 文件:

// ------------------------------------------------bintree.h-------------------------------------------------------
//
// Programmer Name: Aviv Weinstein
// Course Section Number: CSS 502 A
// Creation Date: 1/17/21
// Date of Last Modification: 1/27/21
// Instructor Name: Professor Dong Si
// --------------------------------------------------------------------------------------------------------------------
// Purpose - 
// --------------------------------------------------------------------------------------------------------------------
// Notes on specifications, special algorithms, and assumptions: 
// --------------------------------------------------------------------------------------------------------------------

#ifndef Bintree_H                                 
#define Bintree_H
#include "nodedata.h"
//We have inlcuded iostream in nodedata.h

using namespace std;

class BinTree{

    friend ostream& operator<<(ostream& out, const BinTree& T);  //Used for output printing of BinTree objects. To display the tree using inorder traversal. 

    public:
        BinTree();                              // constructor
        BinTree(const BinTree &);               // copy constructor
        ~BinTree();                             // destructor, calls makeEmpty  
        bool isEmpty() const;                   // true if tree is empty, otherwise false
        void makeEmpty();                       // make the tree empty so isEmpty returns true
        BinTree& operator=(const BinTree &); //The assignment operator (=) to assign one tree to another
        bool operator==(const BinTree &) const; //Boolean comparison operator for equal
        bool operator!=(const BinTree &) const; //Boolean comparison operator for NOT equal
        bool insert(NodeData*); //inserts a new node, with NodeData, into the BinTree object.
        bool retrieve(const NodeData &, NodeData* &) const; //Looks for a specific node in the BinTree. Returns true if the node exists.
        void displaySideways() const;           // provided below, displays the tree sideways
        int getHeight(const NodeData &) const; //function to find the height of a given value in the tree. 
        void bstreeToArray(NodeData* []); //function to fill an array of Nodedata* by using an inorder traversal of the tree
        void arrayToBSTree(NodeData* []); //function to fill an array of Nodedata* by using an inorder traversal of the tree
        
    private:
        struct Node {
            NodeData* data;                     // pointer to data object
            Node* left;                         // left subtree pointer
            Node* right;                        // right subtree pointer
        };
        Node* root;                             // root of the tree

        //Utility functions
        void sideways(Node*, int) const; //provided below, helper for displaySideways()
        bool insertHelp(Node *nodePointer, NodeData* nodeData); //Helper function for the insert function.
        void postOrderDeleteNode(const Node *node); // Helper function. Deletes all nodes in a BinTree object 
        void inorderHelper(Node *startNode) const; //Helper function for printing out all nodes in a BST using in-order traversal
        void inorderHelperArray(NodeData* a[], Node *startNode) const; //Helper function for the operator == and operator!= functions.
                                                                    //Used to compare BSTs to each other using in-order traversal.
        Node* retrieveHelper(Node *root, const NodeData &nodeData) const; //A hlper function for retriving a node in a BinTree
        int getHeightUtil(Node *node)const; //Helper for performing a recursive calculation of the height of a node in a BinTree.
        void preorderTraversal(Node* node); //Performs a pre-order traversal of a BinTree object. Called by the operator= function.
        void convert(NodeData* a[], int start, int end, Node *root); //Performs a utility function in the arraytoBSTree function.
                                                                    //Selects the correct array indexes to be added next into an array.
};

#endif

【问题讨论】:

  • insertHelp 递归调用自身时,您不会检查返回值。即使insertHelp 失败,BinTree::insert 也将始终返回 true
  • ~NodeData 中不需要任何内容​​,因为该类不分配任何内存。 (顺便说一句,也不需要opertator=、复制构造函数或默认构造函数——它们的作用与自动生成的构造函数相同。)我认为问题出在您的代码中。删除添加到其中的节点是BinTree 的责任,但未显示。
  • @JasperKent,这就是我感到困惑的地方。如何让 BinTree 删除 NodeData?如果有帮助,我可以添加 BinTree 的析构函数。
  • 编译您的 C++ 代码,将 GCC 调用为 g++ -Wall -Wextra -g,然后使用 valgrind。也许也可以使用its address sanitizer。顺便说一句,考虑使用std::set 或至少阅读red-black treesIntroduction to algorithms
  • 无关:我建议你将void buildTree(BinTree&amp; T, ifstream&amp; infile)重命名为std::istream&amp; operator&gt;&gt;(std::istream&amp;, BinTree&amp;) instread。然后,您可以使用常见的instream &gt;&gt; bintree_object任何 类型的istream 填充您的bintree。

标签: c++ memory-leaks destructor dynamic-memory-allocation


【解决方案1】:

每次调用new,都必须有对应的delete。在您的示例中,您调用 new,然后将节点存储在 BinTree 中。我认为一旦树被释放,你就不会从 BinTree 中删除这个节点。存储 std::shared_ptr 而不是原始指针将解决此问题。

使用自动内存管理总是更好

void buildTree(BinTree& T, ifstream& infile) {
    string s;

    for (;;) {
        infile >> s;
        cout << s << ' ';
        if (s == "$$") break;                // at end of one line
        if (infile.eof()) break;             // no more lines of data
        // ptr will be removed automatically when it goes out of scope
        std::shared_ptr<NodeData> ptr = std::make_shared<NodeData>(s);
        // would do a setData if there were more than a string
        
        // tree insertion would increase ptr scope to the scope of a T instance, 
        // so if T is deallocated, all nodes will be deallocated automatically
        T.insert(ptr);
    }
}

如果由于某种原因您不想/不能使用 shared_ptr,则在 buildTree 中不做任何更改,并使 BinTree::insert 在内部将原始指针转换为 std::shared_ptr

bool BinTree::insert(NodeData* nodeData){
    if(root != nullptr){ //if the root of the tree is not NULL, meaning that the BST object exists, we call our insertHelp function.
        insertHelp(this->root, nodeData);
        return true;
    }
    else{ //If there is no root node created in our tree, we create a root node.
        root = new Node;
        root->left = nullptr;
        root->right = nullptr;
        // update ---vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
        root->data = std::shared_ptr<NodeData>(nodeData);
        return true;
    }
    return true;
}

bool BinTree::insertHelp(Node *nodePointer, NodeData* nodeData){
      if(*nodeData < *nodePointer->data){ //Here we are deciding if we need to traverse left or not.
        if(nodePointer->left != nullptr){ //If there is a node to the left, we call insertHelper again but on this node to the left.
            insertHelp(nodePointer->left, nodeData);
        }
        else{ //if there is no node the left, and we need to go left, we create a new left node.
            nodePointer->left = new Node;
            if(!nodePointer->left) return false;
            nodePointer->left->left = nullptr;
            nodePointer->left->right = nullptr;
            nodePointer->left->data = std::shared_ptr<NodeData>(nodeData);
            return true;
        }   
    } 
    else if(*nodeData > *nodePointer->data){//Here we are deciding if we need to traverse right or not.
        if(nodePointer->right != nullptr){ //if there is no node the right, and we need to go left, we create a new left node.
            insertHelp(nodePointer->right, nodeData);
        }
        else{ //if there is no node the right, and we need to go right, we create a new right node.
            nodePointer->right = new Node;
            if(!nodePointer->right) return false;
            nodePointer->right->left = nullptr;
            nodePointer->right->right = nullptr;
            nodePointer->right->data = std::shared_ptr<NodeData>(nodeData);
            return true;
        }
    }
    else{ //catch all case. If something goes wrong, return false.
        return false;
    }
    return true;
}

【讨论】:

  • 我很确定我已经遍历了每个节点并删除了每个节点。但由于某种原因,NodeData 没有被删除。我在帖子中添加了我的析构函数。
  • 很遗憾,我无法编辑 buildTree 函数,如果可以的话,那将是最简单的方法。
  • 在我写这个答案的那一刻,没有makeEmpty 的实现存在问题。我将编辑我的答案。
  • 谢谢!我在 postOrderDeleteNode 函数中也有一个错字。我已经删除了。就是 ``` delete rootNode->data``` 这行代码让我的程序发疯了。
  • @AvivWeinstein 所以,你不能改变buildTree,对吧?可以换BinTree::insert吗?
【解决方案2】:

这归结为您的 insertHelpinsert 函数中的一个错误,以及您没有删除节点的数据对象这一事实。

如果您仔细查看 insert,您会注意到它始终返回 true,因此即使您尝试插入重复项,buildTree 也不会删除该对象。

// we need to take the return values of the recursive calls in this function
bool BinTree::insertHelp(Node* nodePointer, NodeData* nodeData) {
    if (*nodeData < *nodePointer->data) { //Here we are deciding if we need to traverse left or not.
        if (nodePointer->left != nullptr) { //If there is a node to the left, we call insertHelper again but on this node to the left.
            return insertHelp(nodePointer->left, nodeData);
        }
        else { //if there is no node the left, and we need to go left, we create a new left node.
            nodePointer->left = new Node;
            nodePointer->left->left = nullptr;
            nodePointer->left->right = nullptr;
            nodePointer->left->data = nodeData;
            return true;
        }
    }
    else if (*nodeData > *nodePointer->data) {//Here we are deciding if we need to traverse right or not.
        if (nodePointer->right != nullptr) { //if there is no node the right, and we need to go left, we create a new left node.
            return insertHelp(nodePointer->right, nodeData);
        }
        else { //if there is no node the right, and we need to go right, we create a new right node.
            nodePointer->right = new Node;
            nodePointer->right->left = nullptr;
            nodePointer->right->right = nullptr;
            nodePointer->right->data = nodeData;
            return true;
        }
    }
    else { //catch all case. If something goes wrong, return false.
        return false;
    }
    return true;
}

bool BinTree::insert(NodeData* nodeData) {
    if (root != nullptr) { //if the root of the tree is not NULL, meaning that the BST object exists, we call our insertHelp function.
        return insertHelp(this->root, nodeData); // <<<<<<<<<<<<<<< use the return value of the helper function here!!!
    }
    else { //If there is no root node created in our tree, we create a root node.
        root = new Node;
        root->left = nullptr;
        root->right = nullptr;
        root->data = nodeData;
        return true;
    }
    return true;
}

void BinTree::makeEmpty() {
    //You make a tree empty by deleting all notes in a post-order traversal.
    postOrderDeleteNode(this->root); //I will call my private postOrderDelete helper function. I broke things up this way to make the code cleaner.
    this->root = nullptr; // needs to be done just once
}

// ---------------------------------postOrderDeleteNode--------------------------------------------------
// Description: This is the private function that performs a post-order traversal of the nodes in a BinTree
//              and deletes each node by deallocating the memory assigned when each node is created.
// ---------------------------------------------------------------------------------------------------
void BinTree::postOrderDeleteNode(const Node* rootNode) {
    if (rootNode == nullptr) { //Base case, we have an empty tree at this node
        return;
    }
    else {
        postOrderDeleteNode(rootNode->left); //First we delete the left side of the tree
        postOrderDeleteNode(rootNode->right); //Then we delete the right side of the tree.
        delete rootNode->data; // <<<<<<<<<<<<<<<<<<<<<<<<  delete the data too!!!!
        delete rootNode; //We finally delete the root of the entire BST.
        // set root to null it in makeEmpty() instead
    }
}

【讨论】:

    猜你喜欢
    • 2013-11-07
    • 2012-05-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-05
    • 2014-04-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多