Elephant Parade, Part 5: Dangling Pointers and Double Deletes
Okay, are you ready to get rid of the memory leak in our program?
As a reminder, here is the code we came up with before (with memory leak).
— WARNING: The following code has a memory leak! —
int main() {
constexpr size_t ELEPHANTS_TO_ADD = 2;
size_t elephantCapacity = ELEPHANTS_TO_ADD;
Elephant* elephants = new Elephant[ELEPHANTS_TO_ADD];
string anotherElephant = "y";
size_t numElephants = 0;
while (anotherElephant == "y") {
++numElephants;
cout << "Please enter elephant " << numElephants << endl;
string name;
cout << "Name: ";
cin >> name;
int age;
cout << "Age: ";
cin >> age;
Elephant el{name, age};
if (numElephants > elephantCapacity) {
// We ran out of space, so make a bigger array
Elephant* newElephants = new Elephant[elephantCapacity + ELEPHANTS_TO_ADD];
// Now copy the contents
for (size_t i = 0; i < elephantCapacity; ++i) {
newElephants[i] = elephants[i];
}
// Now start using the new array
elephants = newElephants;
elephantCapacity += ELEPHANTS_TO_ADD;
}
elephants[numElephants - 1] = el;
cout << endl << "Another elephant (y/n)? ";
cin >> anotherElephant;
cout << endl;
}
// We'll just assume this works
sortElephantsByName(elephants, elephants + numElephants);
cout << "The elephants are parading!" << endl;
for (size_t i = 0; i < numElephants; ++i) {
cout << elephants[i].getName() << " (" << elephants[i].getAge() << ")" << endl;
}
return 0;
}
— WARNING: The previous code has a memory leak! —
You drew a memory diagram for this code a couple of pages ago, right? If not, really do it now!
Make sure you can model the reallocation process when we run out of space in slow motion.
Can you see the moment when the array leaks?
Dangling-Pointer and Double-Delete Errors
Say we tried to fix the problem like this:
...
if (numElephants > elephantCapacity) {
// We need a bigger array, so get rid of this one
delete[] elephants;
// We ran out of space, so make a bigger array
Elephant* newElephants = new Elephant[elephantCapacity + ELEPHANTS_TO_ADD];
// Now copy the contents
for (size_t i = 0; i < elephantCapacity; ++i) {
newElephants[i] = elephants[i];
}
// Now start using the new array
elephants = newElephants;
elephantCapacity += ELEPHANTS_TO_ADD;
}
...
Key Points:
- The problem is that we deallocate the array before we are done using it.
- A pointer to deallocated memory is often called a dangling pointer.
- Deallocated memory could be overwritten at some point, so
- Following a dangling pointer could cause strange behavior.
- Deallocated memory might not be overwritten right away.
- So it might be hard to notice that something is wrong. Watch out!
A related problem is called a double-delete error. That's when you use a dangling pointer in a delete
statement, which tries to deallocate something that has already been deallocated. Usually this will cause a crash.
Fix It For Real
Answer:
Insert "delete[] elephants;" just before "elephants = newElephants;"
Insert "delete[] elephants;" just before "return 0;"
Here is the code with the memory leak fixed. The inserted lines are highlighted.
int main() {
constexpr size_t ELEPHANTS_TO_ADD = 2;
size_t elephantCapacity = ELEPHANTS_TO_ADD;
Elephant* elephants = new Elephant[ELEPHANTS_TO_ADD];
string anotherElephant = "y";
size_t numElephants = 0;
while (anotherElephant == "y") {
++numElephants;
cout << "Please enter elephant " << numElephants << endl;
string name;
cout << "Name: ";
cin >> name;
int age;
cout << "Age: ";
cin >> age;
Elephant el{name, age};
if (numElephants > elephantCapacity) {
// We ran out of space, so make a bigger array
Elephant* newElephants = new Elephant[elephantCapacity + ELEPHANTS_TO_ADD];
// Now copy the contents
for (size_t i = 0; i < elephantCapacity; ++i) {
newElephants[i] = elephants[i];
}
// We are done with the old array
delete[] elephants;
// Now start using the new array
elephants = newElephants;
elephantCapacity += ELEPHANTS_TO_ADD;
}
elephants[numElephants - 1] = el;
cout << endl << "Another elephant (y/n)? ";
cin >> anotherElephant;
cout << endl;
}
// We'll just assume this works
sortElephantsByName(elephants, elephants + numElephants);
cout << "The elephants are parading!" << endl;
for (size_t i = 0; i < numElephants; ++i) {
cout << elephants[i].getName() << " (" << elephants[i].getAge() << ")" << endl;
}
delete[] elephants;
return 0;
}
Let's walk through it real quick to see why this fixes the problem!
Well, I think that about wraps it up!
Meh.
Seriously? Now what?
I just don't like parades.
…
Actually, I do see some more room for improvement!
Oh?
Sure. In this version, our array stores
Elephant
objects, so we still default initialize and then copyElephant
s.That's true. Maybe it would be better to combine our solutions and store
Elephant*
s, with theElephant
s elsewhere on the heap.And I've been thinking about the "resizing" step.
What about it?
Well, that part is expensive because it copies things. And the array just keeps getting bigger. So copying just keeps getting more expensive.
True.
If we add a lot of space every time, we don't have to resize very often. But maybe then we are wasting space.
Yes, I see. There's a trade-off there. There might be something clever to do with how and when we expand the array.
Well, maybe we'll explore some of those ideas in the future…!
To do:
- Make the array store pointers to avoid copying objects so much.
- Think carefully about how and when we expand the array.
Stay tuned!
Just the Beginning (Optional)
I feel pretty overwhelmed by all of this. Pointers and the heap are complicated!
It's true. It's a lot to take in! You are certainly not alone in feeling that way.
A lot of the foundations we've laid so far (especially memory diagrams) have been to support you at this very moment! Use those tools!
And this stuff is here to stay. It will be part of the lessons and homework from now on.
Oh, man…
No, no. That's good news. There will be lots of opportunities to practice and learn!
By the end of the semester, if we work together, this material will all make a lot more sense.
So please be patient with yourself and take advantage of all of the resources you have to get help!
(When logged in, completion status appears here.)