【问题标题】:How is the C# Stack accessed by the CLR?CLR 如何访问 C# 堆栈?
【发布时间】:2012-07-29 07:07:04
【问题描述】:

这可能是一个非常简单的问题,但我在 SO 上找不到答案,也不知道有人问过答案:

我可以像这样编写一个简单的 c# 方法:

private void foo()
{
   int a = 1;
   int b = 5;
}

如果 CIL 代码(由编译器创建)由公共语言运行时执行,它将在堆栈顶部创建以下字段,而执行控件位于方法内部:

b = 5
a = 1

但是现在,我将访问名为“a”的字段的方法扩展为:

private void foo()
{
   int a = 1;
   int b = 5;
   Console.WriteLine(a);
}

现在 CLR 必须访问一个不在堆栈顶部的字段,但根据 FILO(先进后出)原则,它必须在访问之前处理所请求字段之上的所有字段。

在请求字段“a”上方的堆栈中名为“b”的字段会发生什么情况?

CLR不能删除它,因为它可能被之后的执行方法使用,那么它会发生什么?

AFAIK,存储字段只有两种方法,堆栈或堆。将它移到堆中没有多大意义,因为这将利用 CLR 的堆栈带来的所有好处。 CLR 是否会创建类似于第二个堆栈的东西?

具体是如何工作的?

-编辑-

也许我解释得不够清楚。

如果我写一个这样的方法:

private void foo()
{
   int a = 1;
   int b = 5;
   Console.WriteLine(a);
   Console.WriteLine(b);
}

CLR 首先将 2 个字段写入堆栈,然后再访问它们,但顺序相反。

首先,它必须访问字段“a”,但要访问它,CLR 必须处理位于堆栈上字段“a”之上的字段“b”。它不能只是从堆栈中删除字段“b”,因为它必须在之后访问它。

它是如何工作的?

【问题讨论】:

  • 我没有看到您的问题。调用方法会复制参数并附加堆栈帧。原始堆栈帧被保留。
  • 优化或不优化?在我看来,b 将被删除,从而消除了问题。 :)
  • 这四部分系列解释了 C# 中的堆栈和堆,c-sharpcorner.com/uploadfile/rmcochran/…
  • @asawyer:你是对的,因为我的例子没有足够清楚地反映我的问题。我扩展它们以显示我的问题
  • 可以肯定的是,您说的是堆栈,因为它实际上是在运行时布置在内存中,而不是 CLI 指令操作的堆栈,对吧?

标签: c# clr heap-memory cil stack-memory


【解决方案1】:

变量不是单独堆叠的;堆栈包含“帧”。每个帧都包含当前方法调用所需的所有变量(局部变量、参数等)。因此,在您的示例中,ab 在同一帧中彼此并存,无需删除它们中的任何一个。当 foo 方法完成时,整个堆栈帧从堆栈中弹出,将调用方法的帧留在顶部。

The wikpedia article可能会提供一些启示。

【讨论】:

    【解决方案2】:

    调用堆栈并不是严格意义上的“纯”堆栈,您只能在其中与顶部元素进行交互。在调用堆栈中,您正在堆叠整个函数调用和/或整个变量范围,而不是变量。

    例如,如果调用了一个新函数,例如 foo(),它会将其两个变量 ab 放在堆栈顶部,并且可以完全访问它们。它(通常)不知道堆栈中这些变量下方的任何内容。

    我们来看看这段代码:

    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.
    

    【讨论】:

    • 从技术上讲,在 CIL 中,方法的所有局部变量都在方法开始时分配,编译器还可以将 C# 中的不同局部变量组合为 CIL 中的一个变量。但你所描述的是你如何想象事情发生在更高的层次上。
    【解决方案3】:

    您对堆栈的印象是错误的,它只是在方法调用之间充当堆栈。在一个方法中,栈帧就像一个局部变量数组。托管代码的堆栈框架也没有什么特别之处,它的操作完全就像本机 C 或 C++ 代码中使用的堆栈框架。

    局部变量与 EBP 寄存器(堆栈帧指针)有一个固定的偏移量。该偏移量由 JIT 编译器确定。

    您发布的代码的具体结果是即时编译器中内置的优化器只会消除未使用的局部变量。最后一个示例中的a 变量很可能最终会出现在cpu 寄存器中,并且永远不会出现在堆栈中。标准优化。

    【讨论】:

      【解决方案4】:

      请注意,当您谈论字段时,ab 被称为局部变量

      也许下面的简化逻辑表示可以把事情弄清楚。在调用Console.WriteLine 之前,堆栈的顶部看起来像这样:

      |5| // b
      |1| // a
      

      Console.WriteLine 内部,为其参数添加了一个额外的堆栈帧(称为value,它获取变量a 的副本):

      |1| // value = a
      |5| // b
      |1| // a
      

      Console.WriteLine 返回后,顶帧弹出,栈再次变为:

      |5| // b
      |1| // a
      

      【讨论】:

      • 我将此标记为答案,因为您提供了简单易懂的解释。虽然其他帖子很棒而且更详细,但您的帖子帮助我快速理解了其他答案。谢谢!
      【解决方案5】:

      对于 CLR,最好将局部变量视为编号的“槽”,例如邮箱。存储在这些“槽”中的值是否最终出现在方法的堆栈帧中(这里的其他人已经涵盖了这个概念),存储在 CPU 寄存器中,甚至完全优化出来都是抖动细节。有关详细信息,请参阅 IL Stloc 指令。

      最好考虑运行一个执行堆栈的CLR,根据正在执行的指令弹出和推送值。托管代码如何在 CPU 上运行和执行的底层细节是另一回事,这就是传统的堆栈帧、寄存器和指针解引用重新发挥作用的地方。然而,从 IL 级别的 CLR 的角度来看,这些东西(大部分)是无关紧要的。

      【讨论】:

        【解决方案6】:

        有四个相关但不同的概念:C# 中的局部变量、CIL 中的局部变量、CIL 中的堆栈和本机堆栈。

        请注意,C# 局部变量如何映射到 CIL 以及 CIL 局部变量和堆栈如何映射到本机内存是由实现定义的,因此您不应依赖任何这些。

        您知道 C# 本地变量是什么。它们可以表示为 CIL 局部变量,但它们通常不会进入 CIL 堆栈(C# 编译器中可能有一些优化会这样做)。但也有很少的其他选择:如果不需要,可以完全优化局部变量,或者可以将其编译为类中具有难以描述的名称的字段(lambda 的闭包变量,yield 方法中的变量或 @ 987654322@ 方法)。 此外,即使某些 C# 局部变量被编译为 CIL 局部变量,它们也不必 1:1 映射,因为如果编译器知道这样做是安全的,一个 CIL 局部变量可以用于更多 C# 局部变量。

        在 CIL 中,有局部变量和堆栈。局部变量与堆栈完全分离,并且每个变量都有不同的 CIL 指令。 局部变量用于保存更长时间需要的值,并且可以随时访问每个局部变量。 CIL 堆栈主要包含目前正在使用的值:指令参数及其返回值。在堆栈中,只能访问顶部的值。

        CIL 本地和 CIL 堆栈实际上都放置在本机堆栈上,但它们通常只是在寄存器中,如果它们适合的话。当然,JIT 编译器可以进行任何其他优化。正如其他人所说,当前方法堆栈中的任何值都可以随时访问,而不仅仅是顶部。

        【讨论】:

          猜你喜欢
          • 2011-09-23
          • 2015-11-05
          • 2019-04-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-08-19
          • 2015-12-18
          相关资源
          最近更新 更多