CS 70

Elephant Parade, Part 5: Dangling Pointers and Double Deletes

  • LHS Cow speaking

    Okay, are you ready to get rid of the memory leak in our program?

  • RHS Cow speaking

    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;
        }
...

What's wrong with this “fix”?

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

Give the delete statement(s) necessary to prevent a memory leak and specify where it/they should be in the code.

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;
}
  • LHS Cow speaking

    Let's walk through it real quick to see why this fixes the problem!

  • LHS Cow speaking

    Well, I think that about wraps it up!

  • Goat speaking

    Meh.

  • LHS Cow speaking

    Seriously? Now what?

  • Goat speaking

    I just don't like parades.

  • LHS Cow speaking

  • Cat speaking

    Actually, I do see some more room for improvement!

  • LHS Cow speaking

    Oh?

  • Cat speaking

    Sure. In this version, our array stores Elephant objects, so we still default initialize and then copy Elephants.

  • LHS Cow speaking

    That's true. Maybe it would be better to combine our solutions and store Elephant*s, with the Elephants elsewhere on the heap.

  • Cat speaking

    And I've been thinking about the "resizing" step.

  • LHS Cow speaking

    What about it?

  • Cat speaking

    Well, that part is expensive because it copies things. And the array just keeps getting bigger. So copying just keeps getting more expensive.

  • LHS Cow speaking

    True.

  • Cat speaking

    If we add a lot of space every time, we don't have to resize very often. But maybe then we are wasting space.

  • LHS Cow speaking

    Yes, I see. There's a trade-off there. There might be something clever to do with how and when we expand the array.

  • RHS Cow speaking

    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)

  • Hedgehog speaking

    I feel pretty overwhelmed by all of this. Pointers and the heap are complicated!

  • LHS Cow speaking

    It's true. It's a lot to take in! You are certainly not alone in feeling that way.

  • RHS Cow speaking

    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!

  • LHS Cow speaking

    And this stuff is here to stay. It will be part of the lessons and homework from now on.

  • Hedgehog speaking

    Oh, man…

  • LHS Cow speaking

    No, no. That's good news. There will be lots of opportunities to practice and learn!

  • RHS Cow speaking

    By the end of the semester, if we work together, this material will all make a lot more sense.

  • LHS Cow speaking

    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.)