Why does the use of 'new' cause memory leaks?

26,363

Solution 1

What is happening

When you write T t; you're creating an object of type T with automatic storage duration. It will get cleaned up automatically when it goes out of scope.

When you write new T() you're creating an object of type T with dynamic storage duration. It won't get cleaned up automatically.

new without cleanup

You need to pass a pointer to it to delete in order to clean it up:

newing with delete

However, your second example is worse: you're dereferencing the pointer, and making a copy of the object. This way you lose the pointer to the object created with new, so you can never delete it even if you wanted!

newing with deref

What you should do

You should prefer automatic storage duration. Need a new object, just write:

A a; // a new object of type A
B b; // a new object of type B

If you do need dynamic storage duration, store the pointer to the allocated object in an automatic storage duration object that deletes it automatically.

template <typename T>
class automatic_pointer {
public:
    automatic_pointer(T* pointer) : pointer(pointer) {}

    // destructor: gets called upon cleanup
    // in this case, we want to use delete
    ~automatic_pointer() { delete pointer; }

    // emulate pointers!
    // with this we can write *p
    T& operator*() const { return *pointer; }
    // and with this we can write p->f()
    T* operator->() const { return pointer; }

private:
    T* pointer;

    // for this example, I'll just forbid copies
    // a smarter class could deal with this some other way
    automatic_pointer(automatic_pointer const&);
    automatic_pointer& operator=(automatic_pointer const&);
};

automatic_pointer<A> a(new A()); // acts like a pointer, but deletes automatically
automatic_pointer<B> b(new B()); // acts like a pointer, but deletes automatically

newing with automatic_pointer

This is a common idiom that goes by the not-very-descriptive name RAII (Resource Acquisition Is Initialization). When you acquire a resource that needs cleanup, you stick it in an object of automatic storage duration so you don't need to worry about cleaning it up. This applies to any resource, be it memory, open files, network connections, or whatever you fancy.

This automatic_pointer thing already exists in various forms, I've just provided it to give an example. A very similar class exists in the standard library called std::unique_ptr.

There's also an old one (pre-C++11) named auto_ptr but it's now deprecated because it has a strange copying behaviour.

And then there are some even smarter examples, like std::shared_ptr, that allows multiple pointers to the same object and only cleans it up when the last pointer is destroyed.

Solution 2

A step by step explanation:

// creates a new object on the heap:
new B()
// dereferences the object
*(new B())
// calls the copy constructor of B on the object
B object2 = *(new B());

So by the end of this, you have an object on the heap with no pointer to it, so it's impossible to delete.

The other sample:

A *object1 = new A();

is a memory leak only if you forget to delete the allocated memory:

delete object1;

In C++ there are objects with automatic storage, those created on the stack, which are automatically disposed of, and objects with dynamic storage, on the heap, which you allocate with new and are required to free yourself with delete. (this is all roughly put)

Think that you should have a delete for every object allocated with new.

EDIT

Come to think of it, object2 doesn't have to be a memory leak.

The following code is just to make a point, it's a bad idea, don't ever like code like this:

class B
{
public:
    B() {};   //default constructor
    B(const B& other) //copy constructor, this will be called
                      //on the line B object2 = *(new B())
    {
        delete &other;
    }
}

In this case, since other is passed by reference, it will be the exact object pointed to by new B(). Therefore, getting its address by &other and deleting the pointer would free the memory.

But I can't stress this enough, don't do this. It's just here to make a point.

Solution 3

Given two "objects":

obj a;
obj b;

They won't occupy the same location in memory. In other words, &a != &b

Assigning the value of one to the other won't change their location, but it will change their contents:

obj a;
obj b = a;
//a == b, but &a != &b

Intuitively, pointer "objects" work the same way:

obj *a;
obj *b = a;
//a == b, but &a != &b

Now, let's look at your example:

A *object1 = new A();

This is assigning the value of new A() to object1. The value is a pointer, meaning object1 == new A(), but &object1 != &(new A()). (Note that this example is not valid code, it is only for explanation)

Because the value of the pointer is preserved, we can free the memory it points to: delete object1; Due to our rule, this behaves the same as delete (new A()); which has no leak.


For you second example, you are copying the pointed-to object. The value is the contents of that object, not the actual pointer. As in every other case, &object2 != &*(new A()).

B object2 = *(new B());

We have lost the pointer to the allocated memory, and thus we cannot free it. delete &object2; may seem like it would work, but because &object2 != &*(new A()), it is not equivalent to delete (new A()) and so invalid.

Solution 4

B object2 = *(new B());

This line is the cause of the leak. Let's pick this apart a bit..

object2 is a variable of type B, stored at say address 1 (Yes, I'm picking arbitrary numbers here). On the right side, you've asked for a new B, or a pointer to an object of type B. The program gladly gives this to you and assigns your new B to address 2 and also creates a pointer in address 3. Now, the only way to access the data in address 2 is via the pointer in address 3. Next, you dereferenced the pointer using * to get the data that the pointer is pointing to (the data in address 2). This effectively creates a copy of that data and assigns it to object2, assigned in address 1. Remember, it's a COPY, not the original.

Now, here's the problem:

You never actually stored that pointer anywhere you can use it! Once this assignment is finished, the pointer (memory in address3, which you used to access address2) is out of scope and beyond your reach! You can no longer call delete on it and therefore cannot clean up the memory in address2. What you are left with is a copy of the data from address2 in address1. Two of the same things sitting in memory. One you can access, the other you can't (because you lost the path to it). That's why this is a memory leak.

I would suggest coming from your C# background that you read up a lot on how pointers in C++ work. They are an advanced topic and can take some time to grasp, but their use will be invaluable to you.

Solution 5

In C# and Java, you use new to create an instance of any class and then you do not need to worry about destroying it later.

C++ also has a keyword "new" which creates an object but unlike in Java or C#, it is not the only way to create an object.

C++ has two mechanisms to create an object:

  • automatic
  • dynamic

With automatic creation you create the object in a scoped environment: - in a function or - as a member of a class (or struct).

In a function you would create it this way:

int func()
{
   A a;
   B b( 1, 2 );
}

Within a class you would normally create it this way:

class A
{
  B b;
public:
  A();
};    

A::A() :
 b( 1, 2 )
{
}

In the first case, the objects are destroyed automatically when the scope block is exited. This could be a function or a scope-block within a function.

In the latter case the object b is destroyed together with the instance of A in which it is a member.

Objects are allocated with new when you need to control the lifetime of the object and then it requires delete to destroy it. With the technique known as RAII, you take care of the deletion of the object at the point you create it by putting it within an automatic object, and wait for that automatic object's destructor to take effect.

One such object is a shared_ptr which will invoke a "deleter" logic but only when all the instances of the shared_ptr that are sharing the object are destroyed.

In general, whilst your code may have many calls to new, you should have limited calls to delete and should always make sure these are called from destructors or "deleter" objects that are put into smart-pointers.

Your destructors should also never throw exceptions.

If you do this, you will have few memory leaks.

Share:
26,363
Admin
Author by

Admin

Updated on July 14, 2022

Comments

  • Admin
    Admin almost 2 years

    I learned C# first, and now I'm starting with C++. As I understand, operator new in C++ is not similar to the one in C#.

    Can you explain the reason of the memory leak in this sample code?

    class A { ... };
    struct B { ... };
    
    A *object1 = new A();
    B object2 = *(new B());
    
  • Pubby
    Pubby over 12 years
    Please don't use heap/stack when explaining dynamic/automatic storage.
  • Tom Whittock
    Tom Whittock over 12 years
    It's incredibly bad practice to take the address of a reference to delete an object. Use a smart pointer.
  • Blindy
    Blindy over 12 years
    Incredibly bad practice, eh? What do you think smart pointers use behind the scenes?
  • Luchian Grigore
    Luchian Grigore over 12 years
    @Blindy smart pointers (at least decently implemented ones) use pointers directly.
  • Admin
    Admin over 12 years
    @Pubby why don't use? Because of dynamic/automaic storage is always heap, not stack? And that's why there is no need to detail about stack/heap, am I right?
  • Pubby
    Pubby over 12 years
    @user1131997 Heap/stack are implementation details. They're important to know about, but are irrelevant to this question.
  • Mooing Duck
    Mooing Duck over 12 years
    There's more than automatic and dynamic. There's also static.
  • R. Martinho Fernandes
    R. Martinho Fernandes over 12 years
    @user1131997: glad you made this another question. As you can see it's not very easy to explain in comments :)
  • Mario
    Mario over 12 years
    Well, to be perfectly honest, the whole Idea isn't that great, isn't it? Actually, I'm not even sure where the pattern tried in the OP would actually be useful.
  • mattjgalloway
    mattjgalloway over 12 years
    Hmm I'd like a separate answer to it, i.e. same as mine but replacing the heap/stack with what you think best. I'd be interested to find out how you would prefer to explain it.
  • CashCow
    CashCow over 12 years
    I was thinking the same: we can hack it to not leak but you wouldn't want to do that. object1 doesn't have to leak either, as its constructor could attach itself to some kind of data structure that will delete it at some point.
  • Kos
    Kos about 12 years
    It's always just SO tempting to write those "it's possible to do this but don't" answers! :-) I know the feeling
  • AdamM
    AdamM about 12 years
    I quite like that analogy, its not perfect, but it is definitely a good way of explaining memory leaks to people who are new to it!
  • Stefan
    Stefan over 9 years
    I used this in an interview for a senior engineer at Bloomberg in London to explain memory leaks to a HR girl. I got through that interview because I was able to actually explain memory leaks (and threading issues) to a non programmer in a way she understood.
  • Destructor
    Destructor over 8 years
    @R.MartinhoFernandes: excellent answer. Just One question. Why you used return by reference in the operator* () function?
  • R. Martinho Fernandes
    R. Martinho Fernandes almost 8 years
    @Destructor late reply :D. Returning by reference lets you modify the pointee, so you can do, e.g., *p += 2, like you would with a normal pointer. If it didn't return by reference, it wouldn't mimic a normal pointer's behaviour, which is the intention here.
  • Andy
    Andy about 7 years
    Thanks so much for advising to "store the pointer to the allocated object in an automatic storage duration object that deletes it automatically." If only there were a way to require coders to learn this pattern before they're able to compile any C++!