Arrays of noncopyables/Invalid memory operation
ZombineDev via Digitalmars-d-learn
digitalmars-d-learn at puremagic.com
Wed Feb 17 17:14:00 PST 2016
On Wednesday, 17 February 2016 at 22:20:00 UTC, Matt Elkins wrote:
> So in a different thread someone mentioned that when arrays are
> grown an implicit copy could be called on all the elements, as
> they might need to be copied over to a new, larger block of
> memory. This makes sense, and is expected. However, it got me
> concerned: what if the post-blit was disabled because it is
> illegal to copy the object? I am using a lot of objects exactly
> like this, and wanted to make sure my program wasn't working by
> coincidence since my dynamic arrays are still small for now. So
> I tested:
>
> [code]
> import std.stdio;
> @safe:
>
> bool scopeEnded;
>
> struct Foo
> {
> @disable this(this);
>
> this(int val) {writeln("Constructing: ", val, " (", &this,
> ")"); value = val;}
> ~this() {writeln("Destroying: ", value, " (", &this, ")");
> assert(value == int.init || scopeEnded);}
> int value;
> }
>
> unittest
> {
> Foo[] foos;
> for (auto i = 0; i < 10000; ++i)
> {
> ++foos.length;
> foos[$ - 1] = Foo(i);
> }
>
> writeln("Scope about to end");
> scopeEnded = true;
> }
> [/code]
>
> [output]
> Constructing: 0 (18FDA8)
> Destroying: 0 (18FD6C)
> Constructing: 1 (18FDA8)
> Destroying: 0 (18FD6C)
> Constructing: 2 (18FDA8)
> Destroying: 0 (18FD6C)
>
> ....<snipped>....
>
> Constructing: 410 (18FDA8)
> Destroying: 0 (18FD6C)
> Constructing: 411 (18FDA8)
> Destroying: 0 (18FD6C)
> Constructing: 412 (18FDA8)
> Destroying: 0 (18FD6C)
> Constructing: 413 (18FDA8)
> Destroying: 0 (18FD6C)
> Constructing: 414 (18FDA8)
> Destroying: 0 (18FD6C)
> Constructing: 415 (18FDA8)
>
> Destroying: 0 (18FD6C)
> core.exception.InvalidMemoryOperationError at src\core\exception.d(679): Invalid memory operation
> Constructing: 416 (18FDA8)
> ----------------
> Destroying: 0 (18FD6C)
>
> Constructing: 417 (18FDA8)
> core.exception.InvalidMemoryOperationError at src\core\exception.d(679): Invalid memory operation
> Destroying: 0 (18FD6C)
> Constructing: 418 (18FDA8)
> Destroying: 0 (18FD6C)
> Constructing: 419 (18FDA8)
> Destroying: 0 (18FD6C)
> Constructing: 420 (18FDA8)
> Destroying: 0 (18FD6C)
> Constructing: 421 (18FDA8)
> ----------------
> Destroying: 0 (18FD6C)
> Constructing: 422 (18FDA8)
> Destroying: 0 (18FD6C)
> Constructing: 423 (18FDA8)
>
> ....<snipped>....
>
> Constructing: 506 (18FDA8)
> Destroying: 0 (18FD6C)
> Constructing: 507 (18FDA8)
> Destroying: 0 (18FD6C)
> Constructing: 508 (18FDA8)
> Destroying: 0 (18FD6C)
> Constructing: 509 (18FDA8)
> Destroying: 0 (18FD6C)
> Destroying: 29 (5201F4)
> Program exited with code 1
> [/output]
>
> Note that the invalid memory operation lines change relative
> order in this output, I think maybe it is stderr instead of
> stdout.
>
> So now I'm wondering:
> * Is the fact that this compiles a bug? If copying can happen
> under the hood, shouldn't the @disable this(this) prevent Foo
> being used this way? Or should copying be happening at all (the
> compiler could instead choose to "move" the Foo by blitting it
> and NOT running the destructor...though I don't know whether
> that is a safe choice in the general case)?
From looking at the addresses of the objects it seems like
they're being constructed on the stack (as a temporary object),
copied (blitted) to the array on the heap and then the temporary
object has it's destructor being run. I think this is safe
because you can't access the temporary (only the compiler can see
it), so it's uniqueness is preserved. Unnecessarily running
destructor is either an optimization opportunity or a
manifestation of the bug that you reported here:
https://issues.dlang.org/show_bug.cgi?id=15661. I suggest testing
this code again with a newer compiler (see my answer in the other
thread -
http://forum.dlang.org/post/omfyqfulgyzbrxlzrhvq@forum.dlang.org).
> * What is the invalid memory operation? It doesn't occur if I
> remove the assert or make the assert never fail, so I'm
> guessing it has to do with failing an assert in the destructor?
> This points bothers me less than the previous one because I
> don't expect an assert-failing program to behave nicely anyway.
The "Invalid memory operation" error is thrown only by the GC
(AFAIK) when the user tries something unsupported like allocating
or freeing in a destructor, while a GC collection is being run. I
think that it's not possible to trigger it in @nogc code.
From a short debug session, I managed to find out that it crashes
on the `++foos.length` line, somewhere inside this function:
https://github.com/D-Programming-Language/druntime/blob/e47a00bff935c3f079bb567a6ec97663ba384487/src/rt/lifetime.d#L1265. Unfortunately I currently don't have enough time to investigate further.
Can you please file a bugzilla issue with this test case?
More information about the Digitalmars-d-learn
mailing list