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, where you use a dangling pointer in a delete
statement, which attempts to deallocate something that has already been deallocated. Usually this issue will cause a crash.
Fix It For Real
We need to add delete
statements in two places in the code, inserting delete[] elephants;
- Just before
elephants = newElephants;
, and, - 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 can see some room for improvement!
Tell me MORE!
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…!
Still to come…
- 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're 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.)