Post-blit not being called properly: import std.stdio:writeln; struct Elem { int x = -1; this(int x) { this.x = x; writeln("CTOR ", x); } this(this) { writeln("POSTBLIT ", x); } ~this() { if (x!=-1) writeln("DTOR " , x); } } struct Ctr { Elem[3] arr; Elem[] slice() { return arr; } Elem[3] arrVal() { return arr; } } void main() { auto p = Ctr(); p.arr = [Elem(1), Elem(2), Elem(3)]; { writeln("slice rval -> arr {"); Elem[3] _arr = p.slice; writeln("}"); } { writeln("arr rval -> arr {"); Elem[3] _arr = p.arrVal(); writeln("}"); } } Prints out: CTOR 1 CTOR 2 CTOR 3 slice rval -> arr { } DTOR 3 DTOR 2 DTOR 1 arr rval -> arr { POSTBLIT 1 POSTBLIT 2 POSTBLIT 3 } DTOR 3 DTOR 2 DTOR 1 DTOR 3 DTOR 2 DTOR 1 Note that there are more DTOR's than CTOR/POSTBLITS. I believe dmd should maintain the same number of CTOR+POSTBLIT calls as DTOR calls, or all user invariants get broken.
Specifically, the call to slice bit-blits the elements into the array but does not call postblit!
Oops, this is an ldc bug, not a dmd bug.
DMD from Git master (v2.072.0-devel-20e1c81) seems to call too many postblits/ctors, though, when initialising the Ctr instances and then assigning the Elem literals: --- construct Ctr { POSTBLIT -1 (7FFF5C07F598) POSTBLIT -1 (7FFF5C07F59C) POSTBLIT -1 (7FFF5C07F5A0) } assign arr { CTOR 1 (7FFF5C07F5B4) CTOR 2 (7FFF5C07F5C0) CTOR 3 (7FFF5C07F5C4) } slice rval -> arr { POSTBLIT 1 (7FFF5C07F5D0) POSTBLIT 2 (7FFF5C07F5D4) POSTBLIT 3 (7FFF5C07F5D8) } DTOR 3 (7FFF5C07F5D8) DTOR 2 (7FFF5C07F5D4) DTOR 1 (7FFF5C07F5D0) arr rval -> arr { POSTBLIT 1 (7FFF5C07F520) POSTBLIT 2 (7FFF5C07F524) POSTBLIT 3 (7FFF5C07F528) } DTOR 3 (7FFF5C07F5F0) DTOR 2 (7FFF5C07F5EC) DTOR 1 (7FFF5C07F5E8) DTOR 3 (7FFF5C07F5A0) DTOR 2 (7FFF5C07F59C) DTOR 1 (7FFF5C07F598) --- Note that the CTOR-created instances (7FFF5C07F5B4 and so on) are never destructed. (The output is from a slightly modified test case that prints the struct address in parens: --- import std.stdio:writeln; struct Elem { int x = -1; this(int x) { this.x = x; writeln("CTOR ", x, " (", cast(void*)&this, ")"); } this(this) { writeln("POSTBLIT ", x, " (", cast(void*)&this, ")"); } ~this() { if (x!=-1) writeln("DTOR " , x, " (", cast(void*)&this, ")"); } } struct Ctr { Elem[3] arr; Elem[] slice() { return arr; } Elem[3] arrVal() { return arr; } } void main() { writeln("construct Ctr {"); auto p = Ctr(); writeln("}"); writeln("assign arr {"); p.arr = [Elem(1), Elem(2), Elem(3)]; writeln("}"); { writeln("slice rval -> arr {"); Elem[3] _arr = p.slice; writeln("}"); } { writeln("arr rval -> arr {"); Elem[3] _arr = p.arrVal(); writeln("}"); } } --- )
(In reply to David Nadlinger from comment #3) > Note that the CTOR-created instances (7FFF5C07F5B4 and so on) are never > destructed. I don't think this is really a sign of anything, since the compiler is free to move around instances of the struct.
(In reply to Steven Schveighoffer from comment #4) > I don't think this is really a sign of anything, since the compiler is free > to move around instances of the struct. I'm not sure how to reconcile the number of ctor/postblit calls with the number of dtor calls, though.
I'm not saying there's no issue here, just that the fact that you are not matching the addresses of the structs together doesn't necessarily mean anything bad. The compiler could be running the ctor on some piece of memory, and then just moving into the right spot without calling postblit or dtor (seems wasteful, but certainly legal). BTW, things can likely get a lot clearer if we reduce the array size to 1. No reason to triple the output! BTW, the postblit for the -1 values seems incorrect...
(In reply to Steven Schveighoffer from comment #6) > > BTW, the postblit for the -1 values seems incorrect... Definitely incorrect (dmd): struct Elem { int x = -1; this(this) { writeln("POSTBLIT ", x); } ~this() { writeln("DTOR " , x); } } struct Ctr { Elem[1] arr; } void main() { auto p = Ctr(); } prints: POSTBLIT DTOR Should be either two destructors or no postblit. For "Elem arr" instead of "Elem[1] arr", only the destructor is correctly called once.
(In reply to David Nadlinger from comment #5) > I'm not sure how to reconcile the number of ctor/postblit calls with the > number of dtor calls, though. ctor+postblit=dtor indeed, the original test case correctly tracks dtors by printing value assigned to the x field.
(In reply to Max Samukha from comment #7) > Should be either two destructors or no postblit. For "Elem arr" instead of > "Elem[1] arr", only the destructor is correctly called once. What's happening here is that arrays are constructed by calling _d_arraysetctor(). That works by passing it an instance of the object Elem being constructed, which is the Elem.init object, and then copying it into arr[1]. The copy operation invokes the postblit. The code doing the postblit is here: https://github.com/dlang/druntime/blob/master/src/rt/arrayassign.d#L245 Then when it goes out of scope, the destructor is called. That's why you see a single postblit and a single destructor. The code is not incorrect, this is not a bug. However, it can be improved to skip the postblit for default initialization. Therefore, I'm going to re-categorize this as an enhancement request.
Could you please comment on the other test cases as well? I'm not convinced that they are equivalent (i.e. equivalently valid behaviour).
Another effect you are seeing is default construction, which should account for more destructors and postblits than constructor calls.
https://github.com/dlang/dmd/pull/6774 David, if you feel there are further issues here, please file a separate issue with a minimal example.
The dmd output was correct (ignoring irrelevant POSTBLIT -1) - w.r.t number of ctors/postblits & dtors. The ldc output is now correct in version: LDC - the LLVM D compiler (1.1.0git-3139c87): based on DMD v2.071.2 and LLVM 3.8.0 built with LDC - the LLVM D compiler (31eb0f) So the bug can be closed as fixed afaic.
(In reply to Eyal from comment #13) > So the bug can be closed as fixed afaic. Thanks! But I still think you'll like the PR I made for this, as it improves performance.
Commits pushed to master at https://github.com/dlang/dmd https://github.com/dlang/dmd/commit/10c4f4acb524940cf5747bf6406dd51a13e7fbd2 fix Issue 16197 - Constructors/postblits and destructors don't match up for array initialisation https://github.com/dlang/dmd/commit/ca5a785e5ebe56a1019189801002ea214362a1f6 Merge pull request #6774 from WalterBright/fix16197 fix Issue 16197 - Constructors/postblits and destructors don't match … merged-on-behalf-of: Daniel Murphy <yebblies@gmail.com>
Commits pushed to stable at https://github.com/dlang/dmd https://github.com/dlang/dmd/commit/10c4f4acb524940cf5747bf6406dd51a13e7fbd2 fix Issue 16197 - Constructors/postblits and destructors don't match up for array initialisation https://github.com/dlang/dmd/commit/ca5a785e5ebe56a1019189801002ea214362a1f6 Merge pull request #6774 from WalterBright/fix16197
Commits pushed to newCTFE at https://github.com/dlang/dmd https://github.com/dlang/dmd/commit/10c4f4acb524940cf5747bf6406dd51a13e7fbd2 fix Issue 16197 - Constructors/postblits and destructors don't match up for array initialisation https://github.com/dlang/dmd/commit/ca5a785e5ebe56a1019189801002ea214362a1f6 Merge pull request #6774 from WalterBright/fix16197