1

我承认这个问题显示了很多关于堆栈和堆栈帧的可能误解。希望我在这里问一个正确的问题。

在 C# 中,堆栈帧和局部变量如何处理如下代码。这些案例的有趣之处在于,StartTasks 中的变量 j 和 StartTasks2 中的变量 i 都被任务使用,这些任务很可能在这些变量不再在范围内并且在它们运行的​​堆栈帧之后通常会从堆。

此外,在什么情况下,像 j 这样不断被“重新创建”的局部变量在超出范围后会获得一个全新的内存插槽,就像在 StartTask 中发生的那样,并且该局部变量在哪里存在(即 StartTasks 的堆栈帧,这意味着框架不能被删除,或其他地方)?

void StartTasks() {
    int i = 0;
    while ( i < 10000 ) {
        int j = i;
        Task.Run( () => ExecuteThis( j ) ); // eac
    }
}

void StartTasks2() {
    int i = 0;
    while ( i < 10000 ) {
        Task.Run( () => ExecuteThis( i ) ); // eac
    }
}


void BigBoss() {
    StartTasks();
    StartTasks2();
    NowMakeMoreCalls();
}
4

2 回答 2

5

它们被提升为编译器生成的类的一部分。

对您提供的内容进行简单的反编译给出了一些答案:

[CompilerGenerated]
private sealed class <>c__DisplayClass5
{
  public int i;
  public Program <>__this;

  public <>__DisplayClass5()
  {
    base.<>ctor();
  }

  public void <CStartTasks2>b__3()
  {
    this.<>4__this.ExecuteThis(this.i);
  }
}

编译器生成这个类并将调用类的引用传递给它。它还将关闭的变量存储为实例字段。

所以,回答这个问题..它们没有分配在堆栈上。它们形成一个在编译时定义并在运行时实例化的对象。

于 2013-10-24T22:31:50.770 回答
3

这个问题显示了很多关于堆栈和堆栈帧的可能误解。

你的理解很好,但你只是没有把所有的事实放在一起来做出解释。你已经完成了 90% 的路。

堆栈帧是一个实现细节。不要求将本地实现为堆栈槽。请记住,使本地成为本地的并不是它在堆栈上。它们被称为局部变量,而不是堆栈变量。使局部变量成为局部变量的原因在于它的名称仅在方法内部有意义。

这些案例的有趣之处在于,StartTasks 中的变量 j 和 StartTasks2 中的变量 i 都被任务使用,这些任务很可能在这些变量不再在范围内并且在它们运行的​​堆栈帧之后通常会从堆。

首先,您错误地使用了“范围”一词。“作用域”是一个编译时概念;局部变量的范围是代码文本的区域,在该区域中可以通过其名称访问该变量。您正在使用“范围”作为堆栈帧生命周期的运行时概念。那不是范围;那是一生。

您正确地注意到局部变量的生命周期比堆栈帧的生命周期长。显然这意味着本地不能实现为栈槽。它不是。该局部被实现为一个字段。字段是任务引用的对象的字段。

在什么情况下,像 j 这样不断被“重新创建”的局部变量在超出范围后会获得一个全新的内存插槽

重新使用插槽时会创建不正确的程序!重新使用插槽是一种优化。编译器不会进行创建错误程序的优化。

该局部变量在哪里?

如果已知变量的生命周期与堆栈帧的生命周期相同(或更短),则它可以进入堆栈。如果没有,那么它必须放在堆上。

于 2013-10-24T22:38:19.753 回答