Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/loops/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
CLR如何访问C#堆栈?_C#_Clr_Heap Memory_Cil_Stack Memory - Fatal编程技术网

CLR如何访问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代码(由编译器创建)

这可能是一个非常简单的问题,但我在这里找不到答案,所以我也不认识任何人,我问了一个答案:

我可以写一个简单的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代码(由编译器创建)由公共语言运行库执行,则当执行控件位于方法内部时,它将在堆栈顶部创建以下字段:

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指令来处理它们。 使用局部变量