www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.internals - Strange behavior when appending pointers of structs to arrays

reply Jonathan M. Wilbur <jonathan wilbur.space> writes:
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
parent Mike Parker <aldacron gmail.com> writes:
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;
Feb 03