[cfe-dev] operator delete[] does not call derived class destructor

M.E. O'Neill oneill at cs.hmc.edu
Sun May 20 13:05:23 PDT 2012


I think the lesson for clang development is that this code should have produced a warning.  Could it ever make sense to do a derived-to-base conversion on a pointer that came out of array new?  I don't think so.

bd satish wrote:
> Since it works for scalar cases, like,
> 
> Base* ptr = new Derived;
> delete ptr;
> 
> as an end user, I expect the array version also to behave as such. I wonder why the standards decided to make this as "undefined" behavior.

To understand why, let's think about what the operations do.  First, the non-array case, what delete does is:

- Call the destructor (possibly via dynamic dispatch)
- call operator delete (void* ptr), where ptr is the start of the object

In contrast, for arrays, what it needs to do is:

- Call the destructors for all the objects in the array (starting at the *end* of the array and working backwards)
- call operator delete [] (void* ptr), where ptr start of the memory block previously allocated with operator new[]

It's the first of these things that is problematic.  How can the compiler know where that last element is.  It does know that the array has three things in it, but it can't use n*sizeof(Base) to calculate the value it needs, which is n*sizeof(Derived).

In general, this is why Derived[] is not a subtype of Base[] -- array indexing just doesn't work.  Thus, what you're trying to do is *fundamentally misguided*.  You need to work an array of pointers to objects (which is what Java does under the covers with its arrays of objects), or be *incredibly* painstakingly careful.

    M.E.O.

P.S.  For basic C++ questions, you might be better off asking on a site devoted to such things, such as StackOverflow, rather than the a compiler development mailing list.

P.P.S.  But, for fun witnessing undefined behavior, try this:

#include <iostream>

class Base {
public:
    Base()
    {
        std::cout << "Creating Base at " << this << std::endl;
    }

    virtual ~Base()
    {
        std::cout << "Destroying Base at " << this << std::endl;
    }
};

class Derived: public Base {
public:
    Derived()
    {
        std::cout << "Creating Derived at " << this << std::endl;
    }

    ~Derived()  {
        std::cout << "Destroying Derived at " << this << std::endl;
    }
    
    int stuff_;		// sizeof(Derived) > sizeof(Base) now
};

int main()
{

    Base* ptr = new Derived[3];
    delete [] ptr;
    return 0;
}

It produces

Creating Base at 0x1072008f8
Creating Derived at 0x1072008f8
Creating Base at 0x107200908
Creating Derived at 0x107200908
Creating Base at 0x107200918
Creating Derived at 0x107200918
Destroying Base at 0x107200908
Destroying Base at 0x107200900
Destroying Base at 0x1072008f8

Notice that it destroys objects at the wrong places.  It destroys an object at 0x107200900 where none exists, and never touches the one at 0x107200918.






More information about the cfe-dev mailing list