Dynamic arrays of structs initialized by array literals go out of scope without calling destructors. This does not happen with static arrays.
import std.stdio : writefln;
struct S
{
int i;
this(this) { writefln("%X postbit", i); i = 0;}
~this() { writefln("%X dtor", i); }
}
void main()
{
S[] arr = [S()];
}
Issue is maked as dmd issue, because druntime cannot call destructors when AA array goes out of the scope.
Comment #1 by monarchdodra — 2013-01-17T08:39:31Z
(In reply to comment #0)
> Dynamic arrays of structs initialized by array literals go out of scope without
> calling destructors. This does not happen with static arrays.
>
> import std.stdio : writefln;
>
> struct S
> {
> int i;
> this(this) { writefln("%X postbit", i); i = 0;}
> ~this() { writefln("%X dtor", i); }
> }
>
> void main()
> {
> S[] arr = [S()];
> }
>
> Issue is maked as dmd issue, because druntime cannot call destructors when AA
> array goes out of the scope.
Same answer as in http://d.puremagic.com/issues/show_bug.cgi?id=9334.
The array is allocated dynamically, and makes no promises it will release at the end of the scope, or of the program.
I'm not sure what you mean by "array literals", but you'll get the same behavior with:
S[] arr = new S[](5);
Comment #2 by maxim — 2013-01-17T10:07:54Z
(In reply to comment #1)
> Same answer as in http://d.puremagic.com/issues/show_bug.cgi?id=9334.
>
> The array is allocated dynamically, and makes no promises it will release at
> the end of the scope, or of the program.
>
> I'm not sure what you mean by "array literals", but you'll get the same
> behavior with:
> S[] arr = new S[](5);
Well, situation here is different than in issue 9334
import core.stdc.stdio : printf;
struct S
{
int i;
this(this) { printf("%X postbit\n", i); i = 0;}
~this() { printf("%X dtor\n", i); }
}
void main()
{
S[] arr = [S()];
}
Dump of assembler code for function _Dmain:
0x0000000000418894 <+0>: push %rbp
0x0000000000418895 <+1>: mov %rsp,%rbp
0x0000000000418898 <+4>: sub $0x10,%rsp
0x000000000041889c <+8>: movabs $0x1,%rsi
0x00000000004188a6 <+18>: movabs $0x6362a0,%rdi
0x00000000004188b0 <+28>: callq 0x41a610 <_d_arrayliteralTX>
0x00000000004188b5 <+33>: xor %ecx,%ecx
0x00000000004188b7 <+35>: mov %ecx,-0x8(%rbp)
0x00000000004188ba <+38>: lea -0x8(%rbp),%rsi
0x00000000004188be <+42>: mov %rax,%rdi
0x00000000004188c1 <+45>: movsb %ds:(%rsi),%es:(%rdi)
0x00000000004188c2 <+46>: movsb %ds:(%rsi),%es:(%rdi)
0x00000000004188c3 <+47>: movsb %ds:(%rsi),%es:(%rdi)
0x00000000004188c4 <+48>: movsb %ds:(%rsi),%es:(%rdi)
0x00000000004188c5 <+49>: mov %rax,%rdx
0x00000000004188c8 <+52>: movabs $0x1,%rax
0x00000000004188d2 <+62>: mov %rcx,%rax
0x00000000004188d5 <+65>: leaveq
0x00000000004188d6 <+66>: retq
End of assembler dump.
Array literal is allocated by _d_arrayliteralTX, yet is initialized by a stack temporary S(). Where a dtor call on it? The behavior is in contrast to issue 9334 when a dtor in similar situation is called.
Comment #3 by temtaime — 2013-06-25T02:00:59Z
Dump.
Can someone fix that and 9334? It's critical bug for me.
Comment #4 by yebblies — 2013-06-28T05:43:04Z
This is a druntime problem. The destructor should be called for each element in the array when it is collected by the GC.
There is no need to call the destructor or postblit when moving the stack allocated structs to the heap, it is legal to move them with memcpy.
Comment #5 by temtaime — 2013-06-28T05:48:19Z
Stop. Why GC collects it?
Destructor should be called when declaration scope ends.
This is array of structs, not of classes.
Comment #6 by yebblies — 2013-06-28T06:04:08Z
(In reply to comment #5)
> Stop. Why GC collects it?
> Destructor should be called when declaration scope ends.
> This is array of structs, not of classes.
void main()
{
S[] arr = [S()];
}
This is an array of structs, and the array is on the heap.
This would be an array on the stack:
void main()
{
S[1] arr = [S()];
}
Comment #7 by schveiguy — 2013-06-28T07:11:09Z
The GC does not have any idea what type of data is in this block. It cannot call dtors, all it sees is a void * with no indication that it has a finalizer. In order for this to work, we need better record keeping by the GC.
It's definitely a druntime/GC problem, not a dmd problem.
Perhaps Rainer's precise GC stores enough information? not sure.
Comment #8 by maxim — 2013-06-28T07:42:41Z
(In reply to comment #4)
>
> There is no need to call the destructor or postblit when moving the stack
> allocated structs to the heap, it is legal to move them with memcpy.
Issue here is not in non-calling destructor when object is moved from stack to heap by pointer but is in non-calling destructor for temporary. This contradicts to policy in issue 9334 (which is itself not stable).
General problem here is that dmd (unexpectedly) sometimes initializes variables with temporaries and sometimes not. When it does initialize objects with a temporary, sometimes it calls destructors on it and sometimes not. Such game with temporaries and destructors causes bugs.
Comment #9 by yebblies — 2013-06-28T07:55:21Z
(In reply to comment #8)
> (In reply to comment #4)
> >
> > There is no need to call the destructor or postblit when moving the stack
> > allocated structs to the heap, it is legal to move them with memcpy.
>
> Issue here is not in non-calling destructor when object is moved from stack to
> heap by pointer but is in non-calling destructor for temporary. This
> contradicts to policy in issue 9334 (which is itself not stable).
>
The temporary you are talking about is the struct that is being moved to the heap. D allows a struct to be moved with a memcpy. 9334 aside, what is happening here is valid.
1. Structs are constructed on the stack
2. Heap array space is allocated
3. Structs are bit-copied into the heap memory
4. The original stack structs are abandoned
Because only one copy of the structs exists at the end, there is no copy, only a move.
> General problem here is that dmd (unexpectedly) sometimes initializes > variables
> with temporaries and sometimes not. When it does initialize objects with a
temporary, sometimes it calls destructors on it and sometimes not. Such game
> with temporaries and destructors causes bugs.
dmd is free to choose either making a copy (which means calling the postblit, then the destructor of the temporary) or to do a move (bitcopy) like it is doing here. The main difference is that a move has better performance. Both are valid.
Comment #10 by stanislav.blinov — 2018-11-23T10:42:27Z
The array from the original example is getting finalized after main's scope ends; this is since 2.067.