D issues are now tracked on GitHub. This Bugzilla instance remains as a read-only archive.
Issue 9334 - Dtor and postblit for struct heap object are not always called
Summary: Dtor and postblit for struct heap object are not always called
Status: RESOLVED WORKSFORME
Alias: None
Product: D
Classification: Unclassified
Component: dmd (show other issues)
Version: D2
Hardware: All All
: P3 normal
Assignee: No Owner
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2013-01-17 08:22 UTC by Maxim Fomin
Modified: 2023-01-31 11:07 UTC (History)
4 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this issue.
Description Maxim Fomin 2013-01-17 08:22:18 UTC
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();
}
Comment 1 monarchdodra 2013-01-17 08:35:18 UTC
(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.
Comment 2 Maxim Fomin 2013-01-17 09:47:29 UTC
(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.
Comment 3 Denis Shelomovskii 2013-11-07 10:29:18 UTC
(In reply to comment #2)
> 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.

Think deeper. Unlike C++, you can't initialize structs in D like this: `S s(...);`. And it isn't documented that `S s = S(...);` will not create temporaries. See Issue 9002.
Comment 4 RazvanN 2023-01-31 11:07:40 UTC
This has been fixed. Running the code in the OP I get:

0 dtor

It seems that the memory is allocated and it is destroyed at the end of the program. This is the correct behavior.