这可能是一个非常简单的问题,但我在SO上找不到答案,也不知道有人问我答案:
我可以这样写一个简单的c#方法:
private void foo()
{
int a = 1;
int b = 5;
}
如果CIL-Code(由编译器创建)由公共语言运行时执行,它将在堆栈顶部创建以下字段,而执行控件在方法内:
b = 5
a = 1
但是现在,我扩展了Method来访问名为“a”的字段:
private void foo()
{
int a = 1;
int b = 5;
Console.WriteLine(a);
}
现在CLR必须访问不在堆栈顶部的字段,但根据FILO(先进先出)原则,它必须在访问之前处理请求字段之上的所有字段。
在请求字段“a”上方的堆栈上名为“b”的字段会发生什么?
CLR无法删除它,因为之后执行方法可能会使用它,那么会发生什么呢?
AFAIK,只有两种方法可以存储字段,堆栈或堆。将它移到堆中将没有多大意义,因为这将从CLR堆叠中获得所有好处。 CLR是否会创建类似第二个堆栈的东西?
这是如何运作的?
-edit -
也许我没有清楚地解释我的意图。
如果我写一个像这样的方法:
private void foo()
{
int a = 1;
int b = 5;
Console.WriteLine(a);
Console.WriteLine(b);
}
CLR首先在堆栈上写入2个字段,然后以相反的顺序访问它们。
首先,它必须访问字段“a”,但为了达到它,CLR必须处理位于堆栈上字段“a”之上的字段“b”。它不能从堆栈中删除字段“b”,因为它必须在之后访问它。
这是如何工作的?
答案 0 :(得分:5)
变量不是单独堆叠的;堆栈包含“框架”。每个帧包含当前方法调用所需的所有变量(本地,参数等)。因此,在您的示例中,a
和b
在同一帧中彼此并存,并且无需删除其中任何一个。当方法foo
完成时,整个堆栈帧从堆栈中弹出,将调用方法的框架保留在顶部。
The wikpedia article可能会提供一些启示。
答案 1 :(得分:3)
调用堆栈不是严格意义上的“纯”堆栈,您只能与顶层元素进行交互。在调用堆栈中,您正在堆叠整个函数调用和/或整个变量范围,而不是变量。
例如,如果调用了一个新函数,比如foo()
,它会将两个变量a
和b
置于堆栈顶部,并具有对它们的完全访问权限。它(通常)不知道堆栈上那些变量以下的任何东西。
我们来看看这段代码:
void foo() { // << Space is allocated on the stack for a and b.
// << Anything in this scope has full access to a and b.
// << But you cannot (normally) access anything from the
// << calling function.
var a = 1;
var b = 2;
if (a == 1) { // << Another variable scope is placed on the stack.
// << From here you can access a, b and c.
var c = 3;
} // << c is removed from the stack.
} // << a, b and anything else in foo() is removed from the stack.
答案 2 :(得分:2)
也许以下 简化 表示可以清理事物。在调用Console.WriteLine
之前,堆栈的顶部看起来像这样:
|5| // b
|1| // a
在Console.WriteLine
内,为其参数添加了一个额外的堆栈框架(称为value
,它获取变量a
的副本):
|1| // value = a
|5| // b
|1| // a
一旦Console.WriteLine返回,弹出顶部框架并再次成为堆栈:
|5| // b
|1| // a
答案 3 :(得分:1)
你有关于堆栈的错误心理形象,它只是在方法调用之间的堆栈。在方法中,堆栈帧的作用类似于局部变量数组。托管代码的堆栈框架也没有什么特别之处,它与本机C或C ++代码中使用的堆栈框架一样完全。
局部变量与EBP寄存器(堆栈帧指针)有固定的偏移量。该偏移量由JIT编译器确定。
您发布的代码的具体结果是,即时编译器中内置的优化器将消除未使用的局部变量。最后一个示例中的a
变量很可能最终出现在cpu寄存器中,而不会出现在堆栈中。标准优化。
答案 4 :(得分:1)
当涉及到CLR时,最好将局部变量视为编号为“slot”的邮箱。存储在那些“槽”中的值是否在方法的堆栈帧中结束(其他人已经涵盖了这个概念),存储在CPU寄存器中甚至完全优化的是抖动细节。有关更多信息,请参阅IL Stloc指令。
最好考虑运行执行堆栈的CLR,并根据正在执行的指令弹出和推送值。管理代码如何在CPU上执行和执行的基本细节是一个单独的问题,这是传统的堆栈帧,寄存器和指针解除引用重新发挥作用的地方。然而,从IL级别的CLR来看,这些事情(大部分)并不重要。
答案 5 :(得分:0)
有四个相关但不同的概念:C#中的局部变量,CIL中的局部变量,CIL中的堆栈和本机堆栈。
请注意C#locals如何映射到CIL以及CIL本地和堆栈映射到本机内存是如何实现定义的,因此您不应该依赖于任何此类。
你知道C#locals是什么。它们可以表示为CIL本地,但它们通常不会继续使用CIL堆栈(C#编译器中可能会进行一些优化)。但是也有很少的其他选择:本地可以完全优化掉,如果不需要,或者可以编译为具有不可言名的类的类中的字段(lambdas的闭包变量,yield
方法中的变量或async
方法)。
此外,即使一些C#本地编译为CIL本地,它们也不必映射1:1,因为如果编译器知道这样做是安全的,那么一个CIL本地可用于更多C#本地。
在CIL中,有局部变量并且有堆栈。局部变量与堆栈完全分离,并且有不同的CIL指令用于处理它们中的每一个。 局部变量用于保持较长时间所需的值,并且可以随时访问每个本地。 CIL堆栈主要包含正在使用的值:指令的参数及其返回值。在堆栈中,只能访问最高值。
CIL本地和CIL堆栈实际上都放在本机堆栈上,但如果它们适合,它们通常只在寄存器中。当然,JIT编译器可以进行任何其他优化。正如其他人所说,当前方法堆栈中的任何值都可以随时访问,而不仅仅是顶层。