Operator delete question


S

snapwink

I am running into this weird behavior with operator delete on an
embedded system, so I am wondering if this is standard C++ behavior or
is my compiler doing something wrong (RVCT 2.2 compiler). Appreciate
your patience as this is a long example:

static void * my_malloc (unsigned int numBytes)
{
printf ("my malloc\n");
return malloc(numBytes);
}

static void my_free (void *bufPtr)
{
printf ("my free\n");
free (bufPtr);
}

/* Interface I am trying to implement */
class ISocket
{
public:
virtual void Release() = 0;
virtual void TCPSockMethod() = 0;
};

/* The implementation design I have is thus:
* All common methods are implemented in class Socket.
* Function specific to TCP sockets are implemented in class
TCPSocket.
* class Socket is still abstract, there are some methods
unimplemented there.
*/
class Socket : public ISocket
/* Dont ask about virtual inheritence :-(
* Our embedded compiler does not support that. */
{
public:
/* This does additional ref cnt management, removing for simplicity
*/
virtual void Release () {printf ("Socket::Release\n"); delete this;}
virtual void TCPSockMethod () = 0;

protected:
Socket() {printf ("Socket: Ctor\n");}
virtual ~Socket() {printf ("Socket: Dtor\n");}
};

/* TCPSocket is the concrete object. This one has overloaded new/
delete
* operators to use my own memory management. */
class TCPSocket : public Socket
{
public:
TCPSocket() {printf ("TCPSock: Ctor\n");}
virtual ~TCPSocket() throw() {printf ("TCPSock: Dtor\n");}
virtual void TCPSockMethod () {printf ("TCP Sock method\n");}
static void* operator new (unsigned int num) {return my_malloc
(num);}
static void operator delete (void *buf) {my_free(buf);}
};

int main()
{
ISocket *pISock = static_cast <ISocket *> (new TCPSocket());
pISock->Release();
return 0;
}


Compiling with GCC and running this, I am getting the expected
results:
my malloc
Socket: Ctor
TCPSock: Ctor
Socket::Release
TCPSock: Dtor
Socket: Dtor
my free

However, compiling with my embedded compiler, I get different results:
after "Socket: Dtor", the global operator delete is getting called
(instead of the one I overloaded). I want to understand what does C++
standard say about this, or is this dependent on compilers?
 
Ad

Advertisements

A

Andrey Tarasevich

snapwink said:
...
int main()
{
ISocket *pISock = static_cast <ISocket *> (new TCPSocket());
pISock->Release();
return 0;
}


Compiling with GCC and running this, I am getting the expected
results:
my malloc
Socket: Ctor
TCPSock: Ctor
Socket::Release
TCPSock: Dtor
Socket: Dtor
my free

However, compiling with my embedded compiler, I get different results:
after "Socket: Dtor", the global operator delete is getting called
(instead of the one I overloaded). I want to understand what does C++
standard say about this, or is this dependent on compilers?

It is not supposed to be dependent on the compiler. The standard says
that a concrete version of overloaded 'operator delete' should be chosen
as if it was looked up from the destructor of the object being destroyed
(i.e. from the "topmost" destructor). In your case everything seems to
be fine with virtual destructors and the correct destructor is indeed
called by the compiler. GCC also selects the proper 'operator delete'.

Meanwhile, your embedded compiler seems to work incorrectly.

It would be interesting to make an extra experiment and see which
'operator delete' your embedded compiler would call if you just deleted
the object immediately with an explicit delete-expression right in 'main'

ISocket *pISock = static_cast <ISocket *> (new TCPSocket());
delete pISock;

just to check if it is somehow confused by a more "convoluted" method of
deletion through 'delete this' used in your original code.
 
F

Francesco S. Carta

I am running into this weird behavior with operator delete on an
embedded system, so I am wondering if this is standard C++ behavior or
is my compiler doing something wrong (RVCT 2.2 compiler). Appreciate
your patience as this is a long example:

static void * my_malloc (unsigned int numBytes)
{
  printf ("my malloc\n");
  return malloc(numBytes);

}

static void my_free (void *bufPtr)
{
  printf ("my free\n");
  free (bufPtr);

}

/* Interface I am trying to implement */
class ISocket
{
public:
  virtual void Release() = 0;
  virtual void TCPSockMethod() = 0;

};

/* The implementation design I have is thus:
 * All common methods are implemented in class Socket.
 * Function specific to TCP sockets are implemented in class
TCPSocket.
 * class Socket is still abstract, there are some methods
unimplemented there.
 */
class Socket : public ISocket
/*  Dont ask about virtual inheritence :-(
 *  Our embedded compiler does not support that. */
{
public:
  /* This does additional ref cnt management, removing for simplicity
*/
  virtual void Release () {printf ("Socket::Release\n"); delete this;}
  virtual void TCPSockMethod () = 0;

protected:
  Socket() {printf ("Socket: Ctor\n");}
  virtual ~Socket() {printf ("Socket: Dtor\n");}

};

/* TCPSocket is the concrete object. This one has overloaded new/
delete
 * operators to use my own memory management. */
class TCPSocket : public Socket
{
public:
  TCPSocket() {printf ("TCPSock: Ctor\n");}
  virtual ~TCPSocket() throw() {printf ("TCPSock: Dtor\n");}
  virtual void TCPSockMethod () {printf ("TCP Sock method\n");}
  static void* operator new (unsigned int num) {return my_malloc
(num);}
  static void  operator delete (void *buf) {my_free(buf);}

};

int main()
{
  ISocket *pISock = static_cast <ISocket *> (new TCPSocket());
  pISock->Release();
  return 0;

}

Compiling with GCC and running this, I am getting the expected
results:
my malloc
Socket: Ctor
TCPSock: Ctor
Socket::Release
TCPSock: Dtor
Socket: Dtor
my free

However, compiling with my embedded compiler, I get different results:
after "Socket: Dtor", the global operator delete is getting called
(instead of the one I overloaded). I want to understand what does C++
standard say about this, or is this dependent on compilers?

Believe me this is all a bit over my head, nonetheless I wanted to
drop in.

Last thing first: the delete operator you have defined should be
called instead of the default one, that has to be a problem of your
embedded compiler. You might check if that compiler has a forum or a
newsgroup, and check whether that issue is already known.

(I'm not saying your question isn't on topic, you asked about the C++
standard, your question is on topic indeed, just suggesting a further
path to follow)

As an aside, I wonder if you can bring it all somewhat closer to C++
by using iostreams and the obvious replacements of malloc (the built-
in new and delete).

One thing that caught my eye is your override of new returning void* -
shouldn't that return TCPSocket*? If you were using 'new', you
wouldn't be in need of casting, but in that case, you wouldn't be in
need of overriding 'new' at all.

Sorry if this is all wrong, I openly admit that I'm not on firm
ground, I'm just taking the chance to check if I'm getting things
right - waiting for more knowledgeable input.

Cheers,
Francesco
 
J

James Kanze

On 9 Ott, 19:22, snapwink <[email protected]> wrote:

[...]
As an aside, I wonder if you can bring it all somewhat closer
to C++ by using iostreams and the obvious replacements of
malloc (the built- in new and delete).

He's working in an embedded environment. Other rules apply; in
particular, it's quite possible that not all of the standard
library is present.
One thing that caught my eye is your override of new returning void* -
shouldn't that return TCPSocket*?
No.

If you were using 'new', you wouldn't be in need of casting,
but in that case, you wouldn't be in need of overriding 'new'
at all.

He is using new. Or at least, he wants to be using new.
Sorry if this is all wrong, I openly admit that I'm not on
firm ground, I'm just taking the chance to check if I'm
getting things right - waiting for more knowledgeable input.

Check out the difference between the operator new function and
the new operator (in an expression). Despite the names, they
are two very different things.
 
F

Francesco S. Carta

On 9 Ott, 19:22, snapwink <[email protected]> wrote:

    [...]
As an aside, I wonder if you can bring it all somewhat closer
to C++ by using iostreams and the obvious replacements of
malloc (the built- in new and delete).

He's working in an embedded environment.  Other rules apply; in
particular, it's quite possible that not all of the standard
library is present.

Right. Didn't think about that. I must put a stronger attention to
separating the language from the library.
He is using new.  Or at least, he wants to be using new.


Check out the difference between the operator new function and
the new operator (in an expression).  Despite the names, they
are two very different things.

I told ya 'twas a bit over my head ;-)

But now maybe I've got it: the overridden operator new function sets
aside the memory to be returned with a void*, then the new operator
will call that overridden function and will somehow "pass" the
returned pointer to the appropriate constructor, is that right, in
layman terms?
 
Ad

Advertisements

J

James Kanze

On 11 Ott, 12:36, James Kanze <[email protected]> wrote:

[...]
I told ya 'twas a bit over my head ;-)
But now maybe I've got it: the overridden operator new function sets
aside the memory to be returned with a void*, then the new operator
will call that overridden function and will somehow "pass" the
returned pointer to the appropriate constructor, is that right, in
layman terms?

More or less. Because the vocabulary is more than a little confusing
(the new operator is not really the same thing as the operator
new), I tend to be very explicit: a new expression will invoke
the operator new function, and pass the returned pointer to the
appropriate constructor (setting up a try/catch block around the
call to the constructor, so that it can call the operator delete
function if the constructor fails).

It's a bit wordier to always have to write out "operator new
function" and "new expression", but I think it pays off in
clarity in the end. (Alternatively, you can take the approach
of the standard, and speak of an allocator function. Knowing
that the name of the allocator function is "operator new".)
 
F

Francesco S. Carta

James Kanze said:
On 11 Ott, 12:36, James Kanze <[email protected]> wrote:

    [...]
I told ya 'twas a bit over my head ;-)
But now maybe I've got it: the overridden operator new function sets
aside the memory to be returned with a void*, then the new operator
will call that overridden function and will somehow "pass" the
returned pointer to the appropriate constructor, is that right, in
layman terms?

More or less.  Because the vocabulary is more than a little confusing
(the new operator is not really the same thing as the operator
new), I tend to be very explicit: a new expression will invoke
the operator new function, and pass the returned pointer to the
appropriate constructor (setting up a try/catch block around the
call to the constructor, so that it can call the operator delete
function if the constructor fails).

It's a bit wordier to always have to write out "operator new
function" and "new expression", but I think it pays off in
clarity in the end.  (Alternatively, you can take the approach
of the standard, and speak of an allocator function.  Knowing
that the name of the allocator function is "operator new".)

Uh, yes, using just "operator new" and "new operator" becomes quite
confusing, using more distinct(ive) wording is better indeed.
 
D

Diego Martins

I am running into this weird behavior with operator delete on an
embedded system, so I am wondering if this is standard C++ behavior or
is my compiler doing something wrong (RVCT 2.2 compiler). Appreciate
your patience as this is a long example:

static void * my_malloc (unsigned int numBytes)
{
  printf ("my malloc\n");
  return malloc(numBytes);

}

static void my_free (void *bufPtr)
{
  printf ("my free\n");
  free (bufPtr);

}

/* Interface I am trying to implement */
class ISocket
{
public:
  virtual void Release() = 0;
  virtual void TCPSockMethod() = 0;

};

you just missed a virtual destructor in your interface...

class TCPSocket { ....
  virtual void Release () {printf ("Socket::Release\n"); delete this;} ....
};


the "delete this" on your Release() call will invoke ISocket::~ISocket
() (a no-op function generated by your compiler)

int main()
{
  ISocket *pISock = static_cast <ISocket *> (new TCPSocket());
  pISock->Release();
  return 0;


Cheers

Diego Martins
HP

I am back from the dead :D
 
J

Joshua Maurice

I am running into this weird behavior with operator delete on an
embedded system, so I am wondering if this is standard C++ behavior or
is my compiler doing something wrong (RVCT 2.2 compiler). Appreciate
your patience as this is a long example:
[snip]

class Socket : public ISocket
/*  Dont ask about virtual inheritence :-(
 *  Our embedded compiler does not support that. */
{
public:
  /* This does additional ref cnt management, removing for simplicity
*/
  virtual void Release () {printf ("Socket::Release\n"); delete this;}

[snip]

Just a design comment / question. This seems kind of convoluted, if I
understand you correctly. Why have an explicit "release" call which
does reference counting? This isn't very RAII. Would it not be better
to have a class ISocket which has single ownership semantics, then use
a shared ptr with a dynamically allocated ISocket instance to get
shared ownership semantics? Ex:
{
shared_ptr<ISocket> socket(new TCPSocket());
/*do whatever with socket, possibly copy-constructing socket to
other shared ptrs.*/

//...

}/*exit the scope. The destructor of shared_ptr will decrement the
shared count, and if it reaches zero, then destroy the ISocket object,
calling the destructor of ISocket, which is virtual, thus calling
~TCPSocket, thus freeing the handle, and thus everyone is happy and
merry.*/

IMO this is far more clear to the end user, far less leak-prone, and
definitely easier on the implementer as well. Also you get the added
bonus of not having a "delete this;" and potentially working around a
(potential?) compiler bug.

RAII, the style of calling deallocation functions only in destructors,
or at least guaranteeing in the presence of an early release that if
the release was "magically skipped" (like with an exception, an early
return, mis-edit of the code, etc.) then a destructor later on would
still take care of it. Moving all deallocation functions to
destructors puts the onus on the implementers of classes to guarantee
a class invariant that if the object dies, then all of its owned
resources will be freed. With that guarantee, then users of the class
just have to worry about object ownership management, which is well
known and easily doable with stack objects and member sub-objects
(including smart pointers and smart-ptr-containers).
 
S

snapwink

I am running into this weird behavior withoperatordeleteon an
embedded system, so I am wondering if this is standard C++ behavior or
is my compiler doing something wrong (RVCT 2.2 compiler). Appreciate
your patience as this is a long example:
[snip]

class Socket : public ISocket
/*  Dont ask about virtual inheritence :-(
 *  Our embedded compiler does not support that. */
{
public:
  /* This does additional ref cnt management, removing for simplicity
*/
  virtual void Release () {printf ("Socket::Release\n");deletethis;}

[snip]

Just a design comment / question. This seems kind of convoluted, if I
understand you correctly. Why have an explicit "release" call which
does reference counting? This isn't very RAII. Would it not be better
to have a class ISocket which has single ownership semantics, then use
a shared ptr with a dynamically allocated ISocket instance to get
shared ownership semantics? Ex:
  {
    shared_ptr<ISocket> socket(new TCPSocket());
    /*do whatever with socket, possibly copy-constructing socket to
other shared ptrs.*/

    //...

  }/*exit the scope. The destructor of shared_ptr will decrement the
shared count, and if it reaches zero, then destroy the ISocket object,
calling the destructor of ISocket, which is virtual, thus calling
~TCPSocket, thus freeing the handle, and thus everyone is happy and
merry.*/

IMO this is far more clear to the end user, far less leak-prone, and
definitely easier on the implementer as well. Also you get the added
bonus of not having a "deletethis;" and potentially working around a
(potential?) compiler bug.

RAII, the style of calling deallocation functions only in destructors,
or at least guaranteeing in the presence of an early release that if
the release was "magically skipped" (like with an exception, an early
return, mis-edit of the code, etc.) then a destructor later on would
still take care of it. Moving all deallocation functions to
destructors puts the onus on the implementers of classes to guarantee
a class invariant that if the object dies, then all of its owned
resources will be freed. With that guarantee, then users of the class
just have to worry about object ownership management, which is well
known and easily doable with stack objects and member sub-objects
(including smart pointers and smart-ptr-containers).

I agree with you on the RAII and scope semantics. That said, the
reason I have an explicit Release() method to perform ref-counting is
as follows:
1. C/C++ compatibility:
Several clients of our API are C clients. In order to support our
interfaces for C clients as well, we export a wrapper interface
implementation. So basically, the following two would be identical
C++ client usage: pISocket->TCPSockMethod();
C client usage: ISocket_TCPSockMethod(pISocket);

2. Remoting between multi-processor OR multi-process-domain
boundaries:
We would usually have a client stub implementation that would perform
the required marshalling for function calls and send it over to the
server. Hence having an explicit Release() method suits better for
clients.

3. Your suggested RAII/scope methods are still followed by many
clients, but that is upto them. The example I gave is a trivial
example which does not follow this.
Clients allocate a interface pointer, and construct a scope object
holding pointer to that interface.
Whenever scope object goes out of scope, the destructor of the scope
object calls Release() method of the interface pointer. That way
refCnt of the interface is correctly managed.
 
Ad

Advertisements

S

snapwink

It is not supposed to be dependent on the compiler. The standard says
that a concrete version of overloaded 'operator delete' should be chosen
as if it was looked up from the destructor of the object being destroyed
(i.e. from the "topmost" destructor). In your case everything seems to
be fine with virtual destructors and the correct destructor is indeed
called by the compiler. GCC also selects the proper 'operator delete'.

Meanwhile, your embedded compiler seems to work incorrectly.

Thanks for the confirmation. I would try to follow up with the
compiler folks to figure out if they can fix this problem.
It would be interesting to make an extra experiment and see which
'operator delete' your embedded compiler would call if you just deleted
the object immediately with an explicit delete-expression right in 'main'

   ISocket *pISock = static_cast <ISocket *> (new TCPSocket());
   delete pISock;

just to check if it is somehow confused by a more "convoluted" method of
deletion through 'delete this' used in your original code.

I did try "delete pISock" directly from main(). This results in the
ISocket destructor being invoked (which is not virtual). ISocket
destructor later calls global free method (I overloaded the global
delete operator as well to reconstruct this).

Interfaces having virtual destructor is a good idea (as shown by above
call). However, it would not be possible to change that since that
affects the interface's V-table structure and breaks binary-
compatibility with prior clients.
 
Ad

Advertisements

S

snapwink

/* Interface I am trying to implement */
you just missed a virtual destructor in your interface...

Yep, I am aware of that. The interfaces we export need to be binary
compatible. If I added a virtual destructor, that would change the
interface's V-table breaking this compatibility. I don't know why the
initial interface we have does not have virtual destructor (guess they
missed it due to some reason), but we would not be able to change
that.
the "delete this" on your Release() call will invoke ISocket::~ISocket
() (a no-op function generated by your compiler)

Are you sure? I am calling "delete this" in Socket class. Which should
result in Socket destructor being invoked directly. And since that is
virtual, it should in-turn result in TCPSocket destructor getting
invoked. At-least on GCC this is what's happening.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top