digitalmars.D.internals - Strange behavior when appending pointers of structs to arrays
- Jonathan M. Wilbur (55/55) Feb 03 2018 Given the following code:
- Mike Parker (76/86) Feb 03 2018 Running at run.dlang.io:
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?
Feb 03 2018
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: 4203068Running at run.dlang.io: children: 1 grandchildren: 10C:\Users\Jonathan\Desktop\bug>dmd .\bug.d -version=bad2 C:\Users\Jonathan\Desktop\bug>bug.exe children: 1 grandchildren: 1children: 1 grandchildren: 140722524752048Why?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;
Feb 03 2018