delegate confusion

Steven Schveighoffer via Digitalmars-d digitalmars-d at puremagic.com
Fri Aug 4 10:18:41 PDT 2017


On 8/4/17 12:57 PM, bitwise wrote:
> I'm confused about how D's lambda capture actually works, and can't find 
> any clear specification on the issue. I've read the comments on the bug 
> about what's described below, but I'm still confused. The conversation 
> there dropped off in 2016, and the issue hasn't been fixed, despite high 
> bug priority and plenty of votes.
> 
> Consider this code:
> 
> void foo() {
>      void delegate()[] funs;
> 
>      foreach(i; 0..5)
>          funs ~= (){ writeln(i); };
> 
>      foreach(fun; funs)
>          fun();
> }
> 
> void bar() {
>      void delegate()[] funs;
> 
>      foreach(i; 0..5)
>      {
>          int j = i;
>          funs ~= (){ writeln(j); };
>      }
>      foreach(fun; funs)
>          fun();
> }
> 
> 
> void delegate() baz() {
>      int i = 1234;
>      return (){ writeln(i); };
> }
> 
> void overwrite() {
>      int i = 5;
>      writeln(i);
> }
> 
> int main(string[] argv)
> {
>      foo();
>      bar();
> 
>      auto fn = baz();
>      overwrite();
>      fn();
> 
>      return 0;
> }
> 
> First, I run `foo`. The output is "4 4 4 4 4".
> So I guess `i` is captured by reference, and the second loop in `foo` 
> works because the stack hasn't unwound, and `i` hasn't been overwritten, 
> and `i` contains the last value that was assigned to it.
> 
> Next I run `bar`. I get the same output of "4 4 4 4 4". While this hack 
> works in C#, I suppose it's reasonable to assume the D compiler would 
> just reuse stack space for `j`, and that the C# compiler has some 
> special logic built in to handle this.
> 
> Now, I test my conclusions above, and run `baz`, `overwrite` and `fn`. 
> The result? total confusion.
> The output is "5" then "1234". So if the lambdas are referencing the 
> stack, why wasn't 1234 overwritten?
> 
> Take a simple C++ program for example:
> 
> int* foo() {
>      int i = 1234;
>      return &i;
> }
> 
> void overwrite() {
>      int i = 5;
>      printf("%d\n", i);
> }
> 
> int main()
> {
>      auto a = foo();
>      overwrite();
>      printf("%d\n", *a);
>      return 0;
> }
> 
> This outputs "5" and "5" which is exactly what I expect, because I'm 
> overwriting the stack space where the first `i` was stored with "5".
> 
> So now, I'm thinking.... D must be storing these captures on the heap 
> then..right? So why would I get "4 4 4 4 4" instead of "0 1 2 3 4" for 
> `foo` and `bar`?
> 
> This makes absolutely no sense at all.

Because the stack frame of foo or bar or baz is stored on the heap 
BEFORE the function is entered. The compiler determines that the stack 
frame will need to be captured, so it captures it on function entry, not 
when the delegate is taken. Then the variable location is reused for the 
loop, and all delegates point at the same stack frame.

This is necessary for cases where the delegate may affect the frame data 
during the function call. For instance:

void foo()
{
    int i;
    auto dg = { ++i;};
    dg();
    dg();
    assert(i == 2);
}

What is needed is to allocate one frame per scope, and have the delegate 
point at the right ones.

Note, the C++ behavior uses dangling stack pointers, and not something 
we want to support in D.

-Steve


More information about the Digitalmars-d mailing list