www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Question about Object.destroy

reply Lambert Duijst <nospam nospam.com> writes:
Hi all,

I have a question about the following piece of code that I wrote 
to experiment with explicit deletion of objects. I am interested 
in this because, even though D has a garbage collection, 
sometimes an object holds a non-memory resource, such as a file 
handle. In these cases explicit calls to a destructor are needed 
if you want deterministic release of resources.

The code I wrote is this :

import std.stdio;

class MyClass
{
   this(int a) {this.a = a;}
   ~this()
   {
     writeln("Destructor call");
   }

   void doSomething()
   {
     writeln("A is ", this.a);
   }
   int a;
};


void main(string args[])
{
   MyClass s = new MyClass(42);
   s.doSomething();
   s.destroy();
   s.doSomething();
}

The output of this program is:

A is 42
Destructor call

And then it segfaults.

This is obviously because of that last call to doSomething
Now the description of Object.destroy mentions that no GC memory 
is freed by Object.destroy. It does, however, put the object in 
an invalid state, but what does that mean and why does my program 
segfault.

Is it because:

  - The GC did decide to run and cleanup memory straight after the 
call to s.destroy()
  - An object being in an invalid state means the program 
segfaults when the object is used ?
  - Another reason..

And more importantly, can this way of programming with explicit 
destroy calls lead to memory corruption due to dangling pointers 
? or will D always let your program segfault whenever a deleted 
object is accessed ?

Please enlighten me, I am just a newbie :-)
Sep 20 2015
next sibling parent ponce <contact gam3sfrommars.fr> writes:
On Sunday, 20 September 2015 at 17:43:01 UTC, Lambert Duijst 
wrote:
 Is it because:

  - The GC did decide to run and cleanup memory straight after 
 the call to s.destroy()
  - An object being in an invalid state means the program 
 segfaults when the object is used ?
  - Another reason..
All methods are virtual by default. My guess would be that the invalid state implies that the virtual table pointer is not valid anymore, hence the crash.
Sep 20 2015
prev sibling parent reply Adam D. Ruppe <destructionator gmail.com> writes:
Very simple: destroy(s) (or s.destroy but i prefer destroy(s)) 
will set the reference it is passed to null because you aren't 
supposed to use it anymore.

So after calling destroy(s), s is null, so it segfaults when you 
try to use it.
Sep 20 2015
parent reply Lambert Duijst <nospam nospam.com> writes:
On Sunday, 20 September 2015 at 18:21:52 UTC, Adam D. Ruppe wrote:
 Very simple: destroy(s) (or s.destroy but i prefer destroy(s)) 
 will set the reference it is passed to null because you aren't 
 supposed to use it anymore.

 So after calling destroy(s), s is null, so it segfaults when 
 you try to use it.
Oh that surprises me a bit, because I read in the list of deprecated features that delete is deprecated and that the right thing to do is to use destroy instead. Also when I print the address of s it gives me some hex number, but not 0 (null). I use writeln("Address of s ", &s) to print the address, not sure if that is correct though. Before and after the explicit destroy I get the same address, which is 7FFF549108D8. Btw I use dmd v2.067 on mac os 10.10.3 Thanks for the quick replies.
Sep 20 2015
next sibling parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Sunday, 20 September 2015 at 18:34:37 UTC, Lambert Duijst 
wrote:
 Oh that surprises me a bit, because I read in the list of 
 deprecated features that delete is deprecated and that the 
 right thing to do is to use destroy instead.
Right. One of the benefits of destroy is that it nulls the reference. Once you destroy it, you aren't supposed to use it again*. The zombie is kept around until the GC reaps it to protect against dangling pointers (somewhat), but you still aren't actually supposed to reuse it. * If you really want to you can cheat by making a separate local variable to hold it before destroy it... destroy only nulls the variable you pass in, but I do not recommend doing this. Instead, you should reconstruct the object, even if implementing an object pool or something.
 I use writeln("Address of s ", &s) to print the address, not 
 sure if that is correct though.
that gives you the address of the local variable, not the reference. For address of the reference, try `cast(void*) s`. An Object in D is like an Object* in C++. So when you & it, you get a pointer to a pointer.
Sep 20 2015
parent reply Lambert Duijst <nospam nospam.com> writes:
On Sunday, 20 September 2015 at 18:39:57 UTC, Adam D. Ruppe wrote:
 On Sunday, 20 September 2015 at 18:34:37 UTC, Lambert Duijst 
 wrote:
 Oh that surprises me a bit, because I read in the list of 
 deprecated features that delete is deprecated and that the 
 right thing to do is to use destroy instead.
Right. One of the benefits of destroy is that it nulls the reference. Once you destroy it, you aren't supposed to use it again*. The zombie is kept around until the GC reaps it to protect against dangling pointers (somewhat), but you still aren't actually supposed to reuse it. * If you really want to you can cheat by making a separate local variable to hold it before destroy it... destroy only nulls the variable you pass in, but I do not recommend doing this. Instead, you should reconstruct the object, even if implementing an object pool or something.
 I use writeln("Address of s ", &s) to print the address, not 
 sure if that is correct though.
that gives you the address of the local variable, not the reference. For address of the reference, try `cast(void*) s`. An Object in D is like an Object* in C++. So when you & it, you get a pointer to a pointer.
Ah, that clarifies a lot ! Thanks. Also does destroy set all references to that object to null in this case there was only 1, but in a real program there can of course be many references to an object. I wasn't interested in reusing an object after it was deleted, but more in the behaviour of a program if you do use it after a deletion. Just want to know if D protects against dangling pointers or is this just something you should never do. If we are not supposed to use Object.destroy anymore then how can we still free non-memory resources that are held by classes (which are typically cg'ed) in a deterministic way ?
Sep 20 2015
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Sunday, 20 September 2015 at 18:52:17 UTC, Lambert Duijst 
wrote:
 Just want to know if D protects against dangling pointers or is 
 this just something you should never do.
The answer is both: it tries to protect you but you still shouldn't do it.
 If we are not supposed to use Object.destroy anymore then how 
 can we still free non-memory resources that are held by classes 
 (which are typically cg'ed) in a deterministic way ?
The function btw is actually destroy(Object). It works as Object.destroy because of the uniform function call syntax feature which will rewrite it. But I recommend doing destroy(Object) because then you get consistent results, even if an interface has its own destroy method. But you can use it, destroy is cool. delete was teh problem because it doesn't provide even the minimal protection the destroy function has. (You can also malloc/free or stack allocate if you really want to take matters into your own hands but then the language basically doesn't help you at all in the dangling pointer problem.)
Sep 20 2015
parent Lambert Duijst <nospam nospam.com> writes:
Thank you, this clarified a lot.
Sep 20 2015
prev sibling parent reply anonymous <anonymous example.com> writes:
On Sunday 20 September 2015 20:34, Lambert Duijst wrote:

 On Sunday, 20 September 2015 at 18:21:52 UTC, Adam D. Ruppe wrote:
[...]
 So after calling destroy(s), s is null, so it segfaults when 
 you try to use it.
[...]
 Also when I print the address of s it gives me some hex number, 
 but not 0 (null).
 I use writeln("Address of s ", &s) to print the address, not sure 
 if that is correct though.
 Before and after the explicit destroy I get the same address, 
 which is 7FFF549108D8.
&s is the address of the variable. We're not interested that but in the reference that's stored in s. You can print that by casting to void*: writeln(cast(void*) s); But that doesn't change either. I think Adam is mistaken here.
Sep 20 2015
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Sunday, 20 September 2015 at 18:41:18 UTC, anonymous wrote:
 But that doesn't change either. I think Adam is mistaken here.
huh, I just checked the source... and you are right, it doesn't set classes to null itself, but does null out the vtable inside. *ppv = null; // zero vptr even if `resetMemory` is false druntime/src/rt/lifetime.d line 1373 So that's why it is segfaulting, the table of virtual functions is nulled after it is destroyed rather than the reference.
Sep 20 2015
parent Lambert Duijst <nospam nospam.com> writes:
On Sunday, 20 September 2015 at 18:50:44 UTC, Adam D. Ruppe wrote:
 On Sunday, 20 September 2015 at 18:41:18 UTC, anonymous wrote:
 But that doesn't change either. I think Adam is mistaken here.
huh, I just checked the source... and you are right, it doesn't set classes to null itself, but does null out the vtable inside. *ppv = null; // zero vptr even if `resetMemory` is false druntime/src/rt/lifetime.d line 1373 So that's why it is segfaulting, the table of virtual functions is nulled after it is destroyed rather than the reference.
Oops, I think I replied too fast, did not read this reply of yours, which answers some of my questions. If the vtable is nulled then this will always lead to a segfault when methods of deleted object are called, which is good because doing so is a programming error anyway, but at least it does not lead to weird memory corruptions etc..
Sep 20 2015