Thread overview
[Issue 9334] New: Dtor and postblit for struct heap object are not always called
Jan 17, 2013
Maxim Fomin
Jan 17, 2013
Maxim Fomin
January 17, 2013
http://d.puremagic.com/issues/show_bug.cgi?id=9334

           Summary: Dtor and postblit for struct heap object are not
                    always called
           Product: D
           Version: D2
          Platform: All
        OS/Version: All
            Status: NEW
          Severity: normal
          Priority: P2
         Component: DMD
        AssignedTo: nobody@puremagic.com
        ReportedBy: maxim@maxim-fomin.ru


--- Comment #0 from Maxim Fomin <maxim@maxim-fomin.ru> 2013-01-17 08:22:18 PST ---
If struct object allocated on heap is default constructed, dtor is not called. If one has non-default initializer, dtor (and postblit) is called.

import std.stdio : writefln;

struct S
{
    int i;
    this(this) { writefln("%X postbit", i); i = 0;}
    ~this() { writefln("%X dtor", i); }
}

auto foo()
{
    S* s = new S(); // add any argument to new to call dtor
}

void main()
{
    foo();
}

-- 
Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
January 17, 2013
http://d.puremagic.com/issues/show_bug.cgi?id=9334


monarchdodra@gmail.com changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |monarchdodra@gmail.com


--- Comment #1 from monarchdodra@gmail.com 2013-01-17 08:35:18 PST ---
(In reply to comment #0)
> If struct object allocated on heap is default constructed, dtor is not called. If one has non-default initializer, dtor (and postblit) is called.
> 
> import std.stdio : writefln;
> 
> struct S
> {
>     int i;
>     this(this) { writefln("%X postbit", i); i = 0;}
>     ~this() { writefln("%X dtor", i); }
> }
> 
> auto foo()
> {
>     S* s = new S(); // add any argument to new to call dtor
> }
> 
> void main()
> {
>     foo();
> }

I don't think so: The postblit (and destructor) you are seeing comes (AFAIK)
from moving a stack allocated S() into the heap, *during* the new.

In both case, the object that is on the heap is never destroyed. D makes no promises that things get destroyed at the end of a run. (again, AFAIK).

Check this out:
//----
import std.stdio;

struct S
{
   int i;
   this(this) { writefln("%X postbit", i); i = 0;}
   ~this() { writefln("%X dtor", i); }
}

auto foo()
{
   S* s = new S(5); // add any argument to new to call dtor
   writeln("here");
}

void main()
{
   foo();
}
//----
5 postbit
5 dtor
here
//----

As you can see, the dtor we are seeing is *NOT* the one that runs at the end of the program.

From a performance point of view, I can question why there is a postblit and a dtor call at all, but it isn't wrong. You probably don't see it on "default construction", because the runtime only copies the T.init value (so no postblit or any of that jazz).

As far as I'm concerned, there is nothing wrong here.

-- 
Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
January 17, 2013
http://d.puremagic.com/issues/show_bug.cgi?id=9334



--- Comment #2 from Maxim Fomin <maxim@maxim-fomin.ru> 2013-01-17 09:47:29 PST ---
(In reply to comment #1)
> I don't think so: The postblit (and destructor) you are seeing comes (AFAIK)
> from moving a stack allocated S() into the heap, *during* the new.

You misunderstood the point. The problem is not that D's GC does not collect structs, the problem is within foo.

The code:

/* to reduce phobos bloat and remove postblit*/
import core.stdc.stdio : printf;

struct S
{
    int i;
    ~this() { printf("%X dtor\n", i); }
}

auto foo()
{
    S* s = new S(); // add any argument to new to call dtor
}

void main()
{
    foo();
}

Dump of assembler code for function _D4main3fooFZv:
   0x0000000000418768 <+0>:    push   %rbp
   0x0000000000418769 <+1>:    mov    %rsp,%rbp
   0x000000000041876c <+4>:    movabs $0x6362a0,%rdi
   0x0000000000418776 <+14>:    callq  0x41a09c <_d_newitemT>
   0x000000000041877b <+19>:    pop    %rbp
   0x000000000041877c <+20>:    retq
End of assembler dump.

As you see there is no stack allocation.

Case #2 add non-default parameter (1)

Dump of assembler code for function _D4main3fooFZv:
   0x0000000000418768 <+0>:    push   %rbp
   0x0000000000418769 <+1>:    mov    %rsp,%rbp
   0x000000000041876c <+4>:    sub    $0x10,%rsp
   0x0000000000418770 <+8>:    movabs $0x6362a0,%rdi
   0x000000000041877a <+18>:    callq  0x41a0c4 <_d_newitemT>
   0x000000000041877f <+23>:    movl   $0x1,-0x8(%rbp)
   0x0000000000418786 <+30>:    lea    -0x8(%rbp),%rsi
   0x000000000041878a <+34>:    mov    %rax,%rdi
   0x000000000041878d <+37>:    movsb  %ds:(%rsi),%es:(%rdi)
   0x000000000041878e <+38>:    movsb  %ds:(%rsi),%es:(%rdi)
   0x000000000041878f <+39>:    movsb  %ds:(%rsi),%es:(%rdi)
   0x0000000000418790 <+40>:    movsb  %ds:(%rsi),%es:(%rdi)
   0x0000000000418791 <+41>:    callq  0x418798 <_D4main3fooFZv+48>
   0x0000000000418796 <+46>:    jmp    0x4187a2 <_D4main3fooFZv+58>
   0x0000000000418798 <+48>:    lea    -0x8(%rbp),%rdi
   0x000000000041879c <+52>:    callq  0x4186f0 <_D4main1S6__dtorMFZv>
   0x00000000004187a1 <+57>:    retq
   0x00000000004187a2 <+58>:    leaveq
   0x00000000004187a3 <+59>:    retq
End of assembler dump.

Now there is S(1) (struct, not pointer - why?) which is written over memory
allocated by new and dtor is called for this stack struct. Note, even if you
pass 0 (which useless because i is zero anyway), dmd still emits dummy code
like above except that there is 0 instead of 1.

However, it need not to create a temporary S(1), just write 1 directly to value returned from new, or in other words the expected code in case #2 is:

Dump of assembler code for function _D4main3fooFZv:
   0x0000000000418768 <+0>:    push   %rbp
   0x0000000000418769 <+1>:    mov    %rsp,%rbp
   0x000000000041876c <+4>:    movabs $0x6362a0,%rdi
   0x0000000000418776 <+14>:    callq  0x41a09c <_d_newitemT>
   <change allocated value>:    movl   $0x1, (%eax)
   0x000000000041877b <+19>:    pop    %rbp
   0x000000000041877c <+20>:    retq
End of assembler dump.

Note, this is not about optimizing, because

auto foo()
{
    S* s = new S(1);
}

have no reason to create a temporary struct and than call destructor on it.

-- 
Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email
------- You are receiving this mail because: -------