February 03, 2018 Strange behavior when appending pointers of structs to arrays | ||||
---|---|---|---|---|
| ||||
Given the following code: import std.stdio : writeln; --- struct Foo { Foo*[] children; void value() { Foo bar = Foo(); version (bad1) { this.children ~= &bar; } version (bad2) { this.children.length += 1u; this.children[$-1] = &bar; } } void bark() { writeln("children: ", this.children.length); writeln("grandchildren: ", this.children[0].children.length); } } void main () { Foo foo = Foo(); version (good) { Foo bar = Foo(); foo.children ~= &bar; } else { foo.value(); } foo.bark(); } --- The resulting executable returns the following outputs: C:\Users\Jonathan\Desktop\bug>dmd .\bug.d -version=bad1 C:\Users\Jonathan\Desktop\bug>bug.exe children: 1 grandchildren: 4203068 C:\Users\Jonathan\Desktop\bug>dmd .\bug.d -version=bad2 C:\Users\Jonathan\Desktop\bug>bug.exe children: 1 grandchildren: 1 C:\Users\Jonathan\Desktop\bug>dmd .\bug.d -version=good C:\Users\Jonathan\Desktop\bug>bug.exe children: 1 grandchildren: 0 Why? |
February 04, 2018 Re: Strange behavior when appending pointers of structs to arrays | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jonathan M. Wilbur | For starters, this sort of question belongs in the Learn forum. Now to your problem. > The resulting executable returns the following outputs: > > C:\Users\Jonathan\Desktop\bug>dmd .\bug.d -version=bad1 > > C:\Users\Jonathan\Desktop\bug>bug.exe > children: 1 > grandchildren: 4203068 Running at run.dlang.io: children: 1 grandchildren: 10 > > C:\Users\Jonathan\Desktop\bug>dmd .\bug.d -version=bad2 > > C:\Users\Jonathan\Desktop\bug>bug.exe > children: 1 > grandchildren: 1 children: 1 grandchildren: 140722524752048 > > Why? Because structs are value types and you're creating them on the stack. Anything created on the stack in a function will eventually be stomped after the function exits, but with structs you also have the fact that the instance is destroyed. You can see this by adding a destructor to your Foo type and sprinkling some writelns around: ====== import std.stdio : writeln; struct Foo { Foo*[] children; ~this() { writeln("Destroyed!"); } void value() { writeln("Entered value."); Foo bar = Foo(); version (bad1) { this.children ~= &bar; } version (bad2) { this.children.length += 1u; this.children[$-1] = &bar; } writeln("Exiting 'value'"); } void bark() { writeln("children: ", this.children.length); writeln("grandchildren: ", this.children[0].children.length); } } void main () { Foo foo = Foo(); version (good) { Foo bar = Foo(); foo.children ~= &bar; } else { writeln("Entering value."); foo.value(); writeln("value exited."); } foo.bark(); writeln("Exiting main"); } ======= Running bad1 & bad2 will print something like this: Entering value. Entered value. Exiting 'value' Destroyed! value exited. children: 1 grandchildren: 10 Exiting main Destroyed! As you can see, the Foo instance created inside value will be destroyed when the function exits, so that by the time you call foo.bark, children[0] is no longer in a valid state -- hence the garbage value for children[0].children.length. If you want bar to persist outside of value, you need to allocate it on the heap: Foo* bar = new Foo; this.children ~= bar; |
Copyright © 1999-2021 by the D Language Foundation