Bug 9334 – Dtor and postblit for struct heap object are not always called

Status
RESOLVED
Resolution
WORKSFORME
Severity
normal
Priority
P3
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2013-01-17T08:22:18Z
Last change time
2023-01-31T11:07:40Z
Assigned to
No Owner
Creator
Maxim Fomin

Comments

Comment #0 by maxim — 2013-01-17T08:22:18Z
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 by monarchdodra — 2013-01-17T08:35:18Z
(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 by maxim — 2013-01-17T09:47:29Z
(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 by verylonglogin.reg — 2013-11-07T10:29:18Z
(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 by razvan.nitu1305 — 2023-01-31T11:07:40Z
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.