通过延迟执行保留超出范围的堆栈变量
本文关键字:堆栈 变量 范围 执行 保留 通过延迟 | 更新日期: 2023-09-27 18:36:52
我承认这个问题显示出很多关于堆栈和堆栈帧的误解。希望我在这里问一个适当的问题。
在 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();
}
它们被提升为编译器生成的类的一部分。
对您提供的内容进行简单的反编译给出了一些答案:
[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);
}
}
编译器生成此类,并将对调用类的引用传递给该类。它还将闭合的 over 变量存储为实例字段。
所以,为了回答这个问题......它们没有在堆栈上分配。它们形成一个在编译时定义并在运行时实例化的对象。
这个问题显示了对堆栈和堆栈帧的许多可能的误解。
你的理解很好,但你只是没有把所有的事实放在一起来得出解释。你已经完成了90%的路程。
堆栈帧是一个实现细节。不需要将本地实现为堆栈插槽。请记住,使本地成为本地的并不是它在堆栈上。它们被称为局部变量,而不是堆栈变量。使本地成为本地的原因是它的名称仅在方法内部有意义。
使这些情况有趣的是,StartTasks 中的变量 j 和 StartTasks2 中的变量 i 都由任务使用,这些任务很可能在这些变量不再在范围内运行,并且在运行它们的堆栈帧通常已从堆栈中弹出之后。
首先,您错误地使用了术语"范围"。"范围"是一个编译时概念;局部变量的作用域是代码文本的区域,可通过其名称访问该变量。 您使用"范围"作为堆栈帧生存期的运行时概念。这不是范围;那是一辈子。
您正确地注意到,局部变量的生存期比堆栈帧的生存期长。显然,这意味着本地不能实现为堆栈插槽。 其实不然。该局部被实现为一个领域。字段是任务引用的对象字段。
在什么条件下,像J这样不断被"重新创建"的局部变量在超出范围后会得到一个全新的内存槽
。
重复使用插槽时会创建一个不正确的程序!重用插槽是一种优化。编译器不会进行创建错误程序的优化。
该局部变量位于何处?
如果已知变量的生存期与堆栈帧的生存期相同(或更短),则可以继续堆栈。如果没有,那么它必须继续堆。