Topic: Deleting arrays as void* pointers


Author: sdm@cs.brown.edu (Scott Meyers)
Date: 3 Feb 92 15:38:42 GMT
Raw View
Consider a class-specific operator new that sloughs off allocations for
subclases to the global new:

    class Airplane { ... };

    void * Airplane::operator new(size_t size)
    {
        if (size != sizeof(Airplane)) return ::new char[size];
        ...
    }

How should the corresponding delete be written?  Here's one possibility:

    void Airplane::operator delete(void *deadObject, size_t size)
    {
        if (size != sizeof(Airplane)) {
            ::delete [] deadObject;
            return;
        }
        ...
    }

Now suppose we have a subclass of Airplane and we allocate, then delete, an
instance of that class:

    // SpecialAirplane inherits new and delete from Airplane.
    class SpecialAirplane: public Airplane { ... };

    SpecialAirplane *pa = new SpecialAirplane;
    delete pa;

The new/delete view of this is:

    ::delete [] (void*) (::new char[sizeof(SpecialAirplane)]);

(The void* cast arises from the formal parameter type of Airplane::operator
delete.)

Is this kosher?  Does it make sense to delete an array of void*?  Or would
this be safer:

    ::delete [] (char*) (void*) (::new char[...]);

If so, the destructor should be written like this:

    void Airplane::operator delete(void *deadObject, size_t size)
    {
        if (size != sizeof(Airplane)) {
            ::delete [] (char*) deadObject;
            return;
        }
        ...
    }

Bottom line: how should a class-specific operator delete call ::delete if
the memory was originally allocated by calling ::new char[...]?

Scott


-------------------------------------------------------------------------------
What do you say to a convicted felon in Providence?  "Hello, Mr. Mayor."




Author: steve@taumet.com (Stephen D. Clamage)
Date: 4 Feb 92 19:01:33 GMT
Raw View
sdm@cs.brown.edu (Scott Meyers) writes:

>Consider a class-specific operator new that sloughs off allocations for
>subclases to the global new:

[some examples deleted]

>Now suppose we have a subclass of Airplane and we allocate, then delete, an
>instance of that class:

>    // SpecialAirplane inherits new and delete from Airplane.
>    class SpecialAirplane: public Airplane { ... };

>    SpecialAirplane *pa = new SpecialAirplane;
>    delete pa;

>The new/delete view of this is:

>    ::delete [] (void*) (::new char[sizeof(SpecialAirplane)]);

>(The void* cast arises from the formal parameter type of Airplane::operator
>delete.)

>Is this kosher?  Does it make sense to delete an array of void*?

The short answer is, "It's not nice to fool the compiler."  [insert
thunder and lightning here]

If you have different mechanisms for allocating space, you need to
keep track of which was used so that de-allocation occurs in the
right way.  In addition, you have to ensure that the right
destructors get called in the right way.  If you toss away type
information by using casts, you may get strange program behavior.

For example,
 ::delete [] (void*) ...
prevents the destructors for the objects in the array from being
called.  If you did something like
 ::delete [] (X*) ...
the destructor for X would be called at intervals of sizeof(X)
starting at the address used, no matter what the type of the
actual array.

You have to use 'delete' with a pointer of the type acquired from the
corresponding 'new', having the pointer value acquired from that 'new'.
For example:
 char *p = new char[size];
 void *q = p;
 delete [] (char*)q;
--

Steve Clamage, TauMetric Corp, steve@taumet.com




Author: sdm@cs.brown.edu (Scott Meyers)
Date: 4 Feb 92 19:39:36 GMT
Raw View
In article <1242@taumet.com> steve@taumet.com (Stephen D. Clamage) writes:
| sdm@cs.brown.edu (Scott Meyers) writes:
| >The new/delete view of this is:
|
| >    ::delete [] (void*) (::new char[sizeof(SpecialAirplane)]);
|
| If you have different mechanisms for allocating space, you need to
| keep track of which was used so that de-allocation occurs in the
| right way.  In addition, you have to ensure that the right
| destructors get called in the right way.  If you toss away type
| information by using casts, you may get strange program behavior.

In *general*, yes, but this is a special case because the objects in
question are chars, so there is no destructor to worry about.  The special
case is important because if you need enough room to hold an object of type
Foo, but you don't want a Foo constructor to be called, the way to do it is
to call new char[sizeof(Foo)].  This is what you do inside a class-specific
operator new (see, for example, BS2, p. 177).  Inside operator delete, then
do you have to cast the void* pointer THAT THE COMPILER PASSES TO YOU AND
THAT YOU MUST ACCEPT AS A VOID* POINTER back to a char* before calling
delete?

| You have to use 'delete' with a pointer of the type acquired from the
| corresponding 'new', having the pointer value acquired from that 'new'.
| For example:
|  char *p = new char[size];
|  void *q = p;
|  delete [] (char*)q;

It makes sense that I "have to," but does the ARM specify that anywhere?
In particular, in the special case of chars (where destructors do not enter
into the situation), do I "have to" in order to guarantee correct program
behavior?

Scott

-------------------------------------------------------------------------------
What do you say to a convicted felon in Providence?  "Hello, Mr. Mayor."




Author: steve@taumet.com (Stephen D. Clamage)
Date: 6 Feb 92 00:23:08 GMT
Raw View
sdm@cs.brown.edu (Scott Meyers) writes:

>... but this is a special case because the objects in
>question are chars, so there is no destructor to worry about.  The special
>case is important because if you need enough room to hold an object of type
>Foo, but you don't want a Foo constructor to be called, the way to do it is
>to call new char[sizeof(Foo)].  This is what you do inside a class-specific
>operator new (see, for example, BS2, p. 177).  Inside operator delete, then
>do you have to cast the void* pointer THAT THE COMPILER PASSES TO YOU AND
>THAT YOU MUST ACCEPT AS A VOID* POINTER back to a char* before calling
>delete?

In this specific case (which is what I guess you were asking about
all along, but I got lost in all the detail) it probably doesn't matter.

We are assured that we can cast from any object pointer (including
char*) to void* with no loss of information, in the sense that we
can cast the void* back to the original type and have the result
compare equal to the original pointer.

In Standard C, the various requirements on types char and void* are
such that void* and char* must have the same representation.  I
don't know what the eventual C++ standard will say about this, but
I suspect it will continue to be true.

I still think proper code should cast the void* according to the
actual type which was allocated.  (It can't hurt.)
--

Steve Clamage, TauMetric Corp, steve@taumet.com