53
#include <queue>
using namespace std;

class Test{
    int *myArray;

        public:
    Test(){
        myArray = new int[10];
    }

    ~Test(){
        delete[] myArray;
    }

};


int main(){
    queue<Test> q
    Test t;
    q.push(t);
}

After I run this, I get a runtime error "double free or corruption". If I get rid of the destructor content (the delete) it works fine. What's wrong?

Raedwald
  • 46,613
  • 43
  • 151
  • 237
Mihai Neacsu
  • 2,045
  • 5
  • 23
  • 37
  • 10
    [Read this](http://stackoverflow.com/questions/4172722/what-is-the-rule-of-three) and then [read this](http://dl.dropbox.com/u/6101039/Modern%20C++.pdf). – chris Dec 28 '12 at 02:22
  • 5
    `Test t();` declares a function, your code won't compile. you need to post real code. – billz Dec 28 '12 at 02:29
  • While the slideshow in the second link has some good points, I don't agree with all of them, in particular the example on page 9 showing a local huge object being returned by value. That invokes copy construction that may be costly, unless you are using C++11 move semantics. – Remy Lebeau Dec 28 '12 at 02:32
  • @RemyLebeau, The slideshow talks about smart pointers like `std::unique_ptr`, which are part of the C++11 standard. I think it's safe to assume it is implying move semantics at the very least, if not RVO. – chris Dec 28 '12 at 02:33
  • 4
    Read [Rule of Three](http://stackoverflow.com/q/4172722/14065) – Martin York Dec 28 '12 at 02:33
  • @chris: `std::unique_ptr` is new in C++11, but `std::shared_ptr` is not. Also, the slideshow mentions `std::array`, which is not new in C++11, either. So I don't think move semantics can be assumed. Even with RVO used, copy assignment would be invoked without C++11 move assignment. – Remy Lebeau Dec 28 '12 at 02:38
  • @RemyLebeau, Both `std::shared_ptr` and `std::array` are only C++11. There may be similar things in boost, but those wouldn't have a `std::` prefix. – chris Dec 28 '12 at 02:39
  • @chris: actually, they were introduced in TR1 and then updated in C++11 to match boost functionality. – Remy Lebeau Dec 28 '12 at 02:44
  • 2
    @RemyLebeau, Fair enough. I never did use TR1, but I see your point. Even then, though, it would be in the `tr1` namespace, not `std` ;) – chris Dec 28 '12 at 02:45
  • Does this even compile? Shouldn't `Test t();` be `Test t`? – Robᵩ Dec 28 '12 at 03:45
  • @Robᵩ Fixed. It compiles and has the same problem now. – Martin York Dec 28 '12 at 05:52
  • 2
    Unfortunately the answers provided below are the wrong way to fix this problem. You should make `myArray` a `std::vector` rather than a `int*` then all the problems are solved (included the correct C++ move constructors are included in std::vector). – Martin York Dec 28 '12 at 05:54
  • @chris, second link is 404, do you mind send a fresh link, it looks interesting. – SAMPro Nov 23 '20 at 14:10
  • 1
    @SAMPro, [Here you go](https://web.archive.org/web/20150106054725/https://dl.dropboxusercontent.com/u/6101039/Modern%20C%2B%2B.pdf). I should really get into the habit of archiving things like that when I link them. – chris Nov 23 '20 at 16:57

6 Answers6

87

Let's talk about copying objects in C++.

Test t;, calls the default constructor, which allocates a new array of integers. This is fine, and your expected behavior.

Trouble comes when you push t into your queue using q.push(t). If you're familiar with Java, C#, or almost any other object-oriented language, you might expect the object you created earler to be added to the queue, but C++ doesn't work that way.

When we take a look at std::queue::push method, we see that the element that gets added to the queue is "initialized to a copy of x." It's actually a brand new object that uses the copy constructor to duplicate every member of your original Test object to make a new Test.

Your C++ compiler generates a copy constructor for you by default! That's pretty handy, but causes problems with pointer members. In your example, remember that int *myArray is just a memory address; when the value of myArray is copied from the old object to the new one, you'll now have two objects pointing to the same array in memory. This isn't intrinsically bad, but the destructor will then try to delete the same array twice, hence the "double free or corruption" runtime error.

How do I fix it?

The first step is to implement a copy constructor, which can safely copy the data from one object to another. For simplicity, it could look something like this:

Test(const Test& other){
    myArray = new int[10];
    memcpy( myArray, other.myArray, 10 );
}

Now when you're copying Test objects, a new array will be allocated for the new object, and the values of the array will be copied as well.

We're not completely out trouble yet, though. There's another method that the compiler generates for you that could lead to similar problems - assignment. The difference is that with assignment, we already have an existing object whose memory needs to be managed appropriately. Here's a basic assignment operator implementation:

Test& operator= (const Test& other){
    if (this != &other) {
        memcpy( myArray, other.myArray, 10 );
    }
    return *this;
}

The important part here is that we're copying the data from the other array into this object's array, keeping each object's memory separate. We also have a check for self-assignment; otherwise, we'd be copying from ourselves to ourselves, which may throw an error (not sure what it's supposed to do). If we were deleting and allocating more memory, the self-assignment check prevents us from deleting memory from which we need to copy.

Martin York
  • 257,169
  • 86
  • 333
  • 562
derekerdmann
  • 17,696
  • 11
  • 76
  • 110
  • I fixed the original question. So I fixed this as well `Test t();` is not a variable declaration but a forward declaration of a function. – Martin York Dec 28 '12 at 05:49
  • Correct explanation of what is going wrong and how to fix it (if you were going to fix it). But it is the wrong solution to the problem. The correct solution in any real code is to change the type of the member `myArray` to `std::vector`. This way you correctly get all the more efficient move components (constructor/assignment) that are defined in C++11. – Martin York Dec 28 '12 at 05:56
  • @LokiAstari - Good point, but I think it's more important to illustrate the actual problem and how to traditionally fix it. Once that's understood, it becomes obvious why the vector is a better solution. – derekerdmann Dec 28 '12 at 06:58
  • Would you say that the compiler did a shallow copy as opposed to a deep copy, which is what the code meant to do? – viki.omega9 Mar 26 '14 at 00:23
  • @viki.omega9 - Yes, it is a shallow copy. The important part is how the compiler's behavior causes this kind of memory error, because deep copies aren't always the desired result. – derekerdmann Mar 26 '14 at 00:35
  • @derekerdmann Indeed. I was trying to think in terms of what the code wanted to do and what the compiler actually did. Thanks! – viki.omega9 Mar 26 '14 at 01:35
  • @derekerdmann: Why you didn't write delete[] myarray; in overloaded assignment operator? otherwise there is memory leak. Isn't it? Correct me If I am wrong – Destructor Jun 24 '15 at 07:37
  • @PravasiMeet - There isn't a memory leak because the object on the right side of the assignment can still be used after this assignment. This implementation copies the value of myArray into the new object's array, rather than transferring the actual array between objects. The array will be freed by `other`'s destructor. If you want to actually move the array between the objects, then you might want to look into *move semantics*: http://stackoverflow.com/questions/3106110/what-are-move-semantics – derekerdmann Jun 24 '15 at 17:18
  • This is broken. `memcpy` copies *bytes* not integers. Better to use `std::copy` (just as efficient but safer). – Galik May 13 '19 at 00:08
17

The problem is that your class contains a managed RAW pointer but does not implement the rule of three (five in C++11). As a result you are getting (expectedly) a double delete because of copying.

If you are learning you should learn how to implement the rule of three (five). But that is not the correct solution to this problem. You should be using standard container objects rather than try to manage your own internal container. The exact container will depend on what you are trying to do but std::vector is a good default (and you can change afterwords if it is not opimal).

#include <queue>
#include <vector>

class Test{
    std::vector<int> myArray;

    public:
    Test(): myArray(10){
    }    
};

int main(){
    queue<Test> q
    Test t;
    q.push(t);
}

The reason you should use a standard container is the separation of concerns. Your class should be concerned with either business logic or resource management (not both). Assuming Test is some class you are using to maintain some state about your program then it is business logic and it should not be doing resource management. If on the other hand Test is supposed to manage an array then you probably need to learn more about what is available inside the standard library.

Community
  • 1
  • 1
Martin York
  • 257,169
  • 86
  • 333
  • 562
4

You are getting double free or corruption because first destructor is for object q in this case the memory allocated by new will be free.Next time when detructor will be called for object t at that time the memory is already free (done for q) hence when in destructor delete[] myArray; will execute it will throw double free or corruption. The reason is that both object sharing the same memory so define \copy, assignment, and equal operator as mentioned in above answer.

Astro - Amit
  • 767
  • 3
  • 15
  • 36
3

You need to define a copy constructor, assignment, operator.

class Test {
   Test(const Test &that); //Copy constructor
   Test& operator= (const Test &rhs); //assignment operator
}

Your copy that is pushed on the queue is pointing to the same memory your original is. When the first is destructed, it deletes the memory. The second destructs and tries to delete the same memory.

Martin York
  • 257,169
  • 86
  • 333
  • 562
Ryan Guthrie
  • 688
  • 3
  • 11
  • The equal operator Ryan is referring to is likel the assignment operator. You need to define a **default constructor**, a **copy constructor**, and a **copy assignment** operator. If you are using C++11, you can also define a **move contructor** and a **move assignment** operator as well. – Remy Lebeau Dec 28 '12 at 02:41
  • Sorry, you don't need it, but when comparing for equality you're generally after the contents of what the pointers point to, not the memory location so I include it most of the time when using pointers. – Ryan Guthrie Dec 28 '12 at 02:41
0

You can also try to check null before delete such that

if(myArray) { delete[] myArray; myArray = NULL; }

or you can define all delete operations ina safe manner like this:

#ifndef SAFE_DELETE
#define SAFE_DELETE(p) { if(p) { delete (p); (p) = NULL; } }
#endif

#ifndef SAFE_DELETE_ARRAY
#define SAFE_DELETE_ARRAY(p) { if(p) { delete[] (p); (p) = NULL; } }
#endif

and then use

SAFE_DELETE_ARRAY(myArray);
tolgayilmaz
  • 3,987
  • 2
  • 19
  • 19
  • Checking for null is redundant because calling `delete` with a null pointer doesn't do anything. Also setting the pointer to null after deleting it is redundant in a destructor because it will be invalidated when the function ends. – Galik May 13 '19 at 00:16
-2

Um, shouldn't the destructor be calling delete, rather than delete[]?

user888379
  • 1,343
  • 12
  • 30
  • No, it was allocated with `new[]`, ergo it needs a `delete[]`. – chris Dec 28 '12 at 02:46
  • 1
    Noooooooo! It is rightly calling `delete [] myArray`. See the new, it is not `new int` but `new int [x]`. – legends2k Dec 28 '12 at 02:46
  • 1
    http://www.parashift.com/c++-faq/delete-array.html Please read this if you didn't understand what the comments are talking about, and why you're getting downvoted (btw, I didn't downvote). – legends2k Dec 28 '12 at 02:48