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