具有队列实现的Malloc指针错误

时间:2013-12-02 15:52:35

标签: c++ string malloc

我正在尝试用C ++实现队列。队列中包含字符串。

我正在使用以下代码:

#include <iostream> 
#include <stdlib.h> 

struct qnode {
    std::string data;//it stores a string
    qnode *link;//and has a pointer to the next node in the structure.
};

class queue { 
    private: 
        qnode *front; // Pointer to first node.
        qnode *rear;  // Pointer to last node.

    public: 
        // Constructor.
        queue();

        //Is the queue empty?
        bool empty() ;

        //peeks first element
        int first(std::string *c);

        // Adds element to the queue.
        int enqueue(std::string c);

        // Extracts an element from the queue.
        int dequeue(std::string *c);

        // Destructor.
        ~queue();
};

//////////// ACTUAL CODE /////////////

//Constructor, front and rear initially point to null.
queue::queue() { 
    front = NULL;
    rear = NULL;
}

// If front points to null then the queue is empty.
bool queue::empty() {
    return (front == NULL);     
}

int queue::first(std::string *c) {
    int res = 1;

    if (!empty()) {
        res = 0;
        *c = front->data;
    }  

    return res;
}

//Function for adding an element to the end of queue.
int queue::enqueue (std::string c){
    int res = 1;

    qnode *tmp; // Auxiliar pointer.
    tmp = new qnode; //Auxiliar new node.

    if(tmp != NULL) {
        // If the queue is empty then we store the data in tmp and make its link point to null and front and rear become tmp.
        if(empty()){
            tmp->data = c;
            tmp->link = NULL;
            front = tmp;
            rear = tmp;
            rear->link = tmp;
        } else {
            // If it is not, then last node's link point to tmp, we store the data in tmp and tmp's link point to null.
            rear->link = tmp;
            tmp->data = c;
            tmp->link = NULL;
            // Rear of the queue points to tmp now.
            rear = tmp;
        }

        res = 0;
    }

    return res;
}

// Function for extracting an element from the queue.
int queue::dequeue(std::string *c){
    int res = 1;

    // We make sure queue is not empty and access first node's data with the help of tmp.
    if(!empty()) {
        qnode *tmp;
        tmp = front;
        //The data extracted is stored in the character container c, passed by reference.
        *c = tmp->data;
        // Front now has to point to the next node and the old element has to be deleted.
        front = front->link;
        delete tmp;

        res = 0;
    }

    return res;
}

// Destructor
queue::~queue() {
    // If it is already empty we don't have to do anything.
    if (empty()){
        return;
    }

    qnode *tmp;

    // We take front element and delete it throught a tmp node till the queue is empty.
    while (!empty()){
        tmp = front;
        front = front->link;
        delete tmp;
    }
}

//////////// MAIN ///////////

int main(int argc, char *argv[]) {
    queue queue;
    std::string str2;

    queue.enqueue("hello");
    queue.dequeue(&str2);

    std::cout << str2;
}

可悲的是,我收到以下错误:

  

$ g ++ -o issue issue.cpp $ ./issue issue(23060,0x7fff71058310)malloc:   对象0x7f962b403920的 *错误:未分配被释放的指针   * 在malloc_error_break中设置断点以调试helloAbort陷阱:6

如果我评论队列析构函数(“while”块),实现工作正常。使用char元素(只有单个字母),实现也很好。

任何人都可以启发我吗?

1 个答案:

答案 0 :(得分:1)

你的问题是你正在初始化你的入队函数中的tmp后面的&gt;链接 - 将行rear->link = tmp更改为rear->link = NULL的空队列部分中的enqueue()和它对我来说很好^^你的最后一个节点不应指向任何东西,当你到达目的地时,你试图删除相同的内存两次,因为那个内存与它自己有关。

编辑:现在是更重要的答案。您的列表和队列实现都不遵循Rule of Big Three。规则的基本纲要是这样的(虽然你应该仍然阅读链接 - 它是C ++中最重要的习语之一):

如果一个类动态地管理资源,它应该提供一个复制构造函数,赋值运算符和析构函数。任何违反此规则的类(即你的)都有冒成指针副本的风险,然后删除它们。在您的示例中,它的外观如下:

您的队列使用您期望的字符串进行初始化。一切都很好看。然后调用函数l.insert(queue, 0),其原型如下:int insert(queue c, int position);

需要注意的重要一点是队列是通过VALUE传递的,这意味着它的COPY(使用复制构造函数 - 三大#1的规则),然后是列表节点使用THAT COPY的数据创建 - 特别是头指针。由于您没有提供复制构造函数,该指针只是按位复制 - 这意味着您的队列副本(您要插入到列表中的数据)具有EXACT SAME头指针作为您传入的队列,但是这是一个不同的对象。

然后,您编写了行tmp->data=c; - 这是您的三大功能中的另一个,赋值运算符。同样,你没有提供一个 - 所以指针再次按位分配,导致与复制构造函数相同的问题。

到目前为止,我们已经传入了三个我们传入的队列对象副本,所有这些副本都有相同的头指针。看看我们要去哪里?

现在,当您的list::insert()函数完成时,它会销毁它创建的所有本地对象。那是什么意思?它意味着调用您按值传递队列时创建的副本的析构函数。因为它具有与你拥有的那个队列的其他两个副本相同的头指针,所以它通过它们的列表以及它自己的列表,删除所有内容。所以现在,你的列表节点的队列数据是垃圾,但我们还没有完全崩溃和完全烧毁。让我们看看当你的程序退出时会发生什么。

现在有趣的开始了 - 你看到的双重免费错误(事实上,它是三重免费的,因为我们制作了三个副本,还记得吗?)。当你的程序退出时,其他副本的析构函数也会被调用 - 他们会尝试遍历列表,再次删除所有内容。

那么,我们如何解决这些问题呢?

提供的链接将为您提供更深入的解释 - 但您需要做的是确保这些类都提供三巨头规则中的所有功能,并且这些功能构成deep copy必要时 - 不是将指针复制到头部(例如),而是将队列设置为new qnode;,将其设置为头部,并使用其他列表头部数据的副本填充其数据。例如(我没有编译它,并且没有错误检查,等等)你的队列复制构造函数可能如下所示:

Queue::Queue(const Queue &other)
{
    head = new qnode; //IMPORTANT: don't copy the value of the pointer (like the compiler
                  // will by default if you let it)
    head->data = other.head->data;

    qnode *tmp = other.head->link;
    while (tmp != nullptr)
    {
        qnode *tmp2 = new qnode; //again, don't copy the value of the pointer!
        tmp2->data = tmp.data; //make a new node and copy the data
        tmp2->link = nullptr;
        rear->link = tmp2; //update the tail of the list
        rear = tmp2;
    }
}

希望有所帮助......它比我预期的打字更多!但是你给出了一个很好的例子,说明为什么三巨头规则是C ++中最重要的习语之一。每当你打破三巨头规则时,这些事情都会发生。