CLR如何访问C#堆栈?
这可能是一个非常简单的问题,但我在这里找不到答案,所以我也不认识任何人,我问了一个答案: 我可以写一个简单的c#方法如下:CLR如何访问C#堆栈?,c#,clr,heap-memory,cil,stack-memory,C#,Clr,Heap Memory,Cil,Stack Memory,这可能是一个非常简单的问题,但我在这里找不到答案,所以我也不认识任何人,我问了一个答案: 我可以写一个简单的c#方法如下: private void foo() { int a = 1; int b = 5; } private void foo() { int a = 1; int b = 5; Console.WriteLine(a); Console.WriteLine(b); } |5| // b |1| // a 如果CIL代码(由编译器创建)
private void foo()
{
int a = 1;
int b = 5;
}
private void foo()
{
int a = 1;
int b = 5;
Console.WriteLine(a);
Console.WriteLine(b);
}
|5| // b
|1| // a
如果CIL代码(由编译器创建)由公共语言运行库执行,则当执行控件位于方法内部时,它将在堆栈顶部创建以下字段:
b = 5
a = 1
但现在,我将访问名为“a”的字段的方法扩展为:
现在CLR必须访问不在堆栈顶部的字段,但根据FILO(先进先出)原则,它必须在访问它之前处理请求字段上方的所有字段
被请求字段“a”上方堆栈上名为“b”的字段会发生什么情况
CLR无法删除它,因为执行方法可能会在以后使用它,那么它会发生什么
另外,只有两种方法可以存储字段、堆栈或堆。将其移动到堆中没有多大意义,因为这将从CLR中获取堆栈的所有好处。CLR是否创建了类似于第二个堆栈的东西
这到底是怎么回事
-编辑-
也许我没有解释清楚我的意图
如果我写一个这样的方法:
private void foo()
{
int a = 1;
int b = 5;
}
private void foo()
{
int a = 1;
int b = 5;
Console.WriteLine(a);
Console.WriteLine(b);
}
|5| // b
|1| // a
CLR首先在堆栈上写入2个字段,然后访问它们,但顺序相反
首先,它必须访问字段“a”,但要访问它,CLR必须处理位于堆栈上字段“a”上方的字段“b”。它不能只是从堆栈中删除字段“b”,因为它必须在以后访问它
这是怎么回事 变量不是单独堆叠的;堆栈包含“帧”。每个帧包含当前方法调用所需的所有变量(局部变量、参数等)。因此,在您的示例中,
a
和b
在同一个框架中并排存在,不需要删除它们中的任何一个。当方法foo
完成时,整个堆栈框架从堆栈中弹出,将调用方法的框架保留在顶部
可能会提供一些启示。调用堆栈并非严格意义上的“纯”堆栈,您只能与顶层元素交互。在调用堆栈中,您正在堆叠整个函数调用和/或整个变量范围,而不是变量 例如,如果调用了一个新函数,例如
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.
void foo(){/请注意,当您谈论字段时,a
和b
称为局部变量
也许下面的简化的逻辑表示法可以解决问题。在调用控制台.WriteLine
之前,堆栈顶部应该是这样的:
private void foo()
{
int a = 1;
int b = 5;
}
private void foo()
{
int a = 1;
int b = 5;
Console.WriteLine(a);
Console.WriteLine(b);
}
|5| // b
|1| // a
在控制台.WriteLine
中,为其参数添加了一个额外的堆栈框架(称为值
,它获取变量a
)的副本:
一旦Console.WriteLine返回,将弹出顶部框架,堆栈再次变为:
|5| // b
|1| // a
你对栈有错误的心理形象,它只在方法调用之间起到堆栈的作用。在一个方法中,堆栈框架就像一个局部变量数组。对于托管代码的堆栈框架也没有什么特殊之处,它的操作与原生C或C++代码中使用的堆栈框架完全一样。
局部变量与EBP寄存器(堆栈帧指针)之间有一个固定的偏移量。该偏移量由JIT编译器确定
您发布的代码的具体结果是,即时编译器中内置的优化器只会消除未使用的局部变量。上一个示例中的a
变量很可能最终会出现在cpu寄存器中,而不会出现在堆栈中。这是一种标准优化。对于CLR,最好将局部变量视为编号的“插槽”,如邮箱。存储在这些“插槽”中的值是否会在方法的堆栈帧中结束(其他人已经介绍了该概念)、存储在CPU寄存器中,甚至完全优化都是抖动详细信息。有关更多信息,请参阅IL指令
最好考虑CLR运行一个执行堆栈,根据执行的指令弹出和推送值。托管代码如何在CPU上JIT和执行的底层细节是另一回事,这是传统堆栈帧、寄存器和指针解引用重新发挥作用的地方。从然而,从IL级别的CLR角度来看,这些东西(大部分)并不重要。有四个相关但不同的概念:C#中的局部变量、CIL中的局部变量、CIL中的堆栈和本机堆栈
请注意,C#locals如何映射到CIL以及CIL locals和堆栈如何映射到本机内存是由实现定义的,所以您不应该依赖这些
你知道什么是C#locals。它们可以表示为CIL locals,但它们通常不会进入CIL堆栈(在C#编译器中可能会有一些这样的优化)但是也有一些其他的选择:局部可以完全优化掉,如果不需要的话,或者它可以被编译成类中的一个字段,具有一个不可描述的名称(lambdas的闭包变量,yield
method或async
methods中的变量)。
此外,即使某些C#local被编译为CIL local,它们也不必映射为1:1,因为如果编译器知道这样做是安全的,一个CIL local可以用于更多的C#local
在CIL中,有局部变量,也有堆栈。局部变量与堆栈完全分离,并且有不同的CIL指令来处理它们。
使用局部变量