堆栈 - 值类型的值存储在哪里?

3
void main()
{
    int x = 5; // stack-allocated
    Console.WriteLine(x);
}

我知道x是栈上分配的。但实际上,关于x在堆栈中存储了什么?它保存的是“真实值”还是指向内存中包含该值的某个位置的地址?


5
Lippert先生可能会警告您,在桌面CLR上的C#的微软实现中,x是被堆栈分配的。请参见http://blogs.msdn.com/b/ericlippert/archive/2010/09/30/the-truth-about-value-types.aspx。(我的意思是:您正在询问一个实现细节,在不同版本、实现、平台或工作日之间可能会悄悄发生变化。) - Jens
1
虽然人们通常(完全正确地)指出这是一个实现细节,但我仍然认为了解CLR在幕后的工作原理很重要。这是一个好问题+1。在这种情况下,x的实际值将存储在堆栈中(或可能在寄存器中)。 - MattDavey
2
“Elide”是一个语法术语,指在不改变句子意义的情况下消除单词。如果我对你说“我有一些需要洗的毛巾”,即使我在“我有一些需要被洗的毛巾”中省略了“I”,“to”和“be”等词,您也能理解我的意思。同样地,在计算机科学中,“elision”是指消除一个实体,以便程序的含义不会改变。您程序中的变量x可以忽略,因为它仅对其值有用,该值在编译时已知,并且作为存储位置没有用处。 - Eric Lippert
2
如果您有兴趣了解.NET中内存管理的真相——至少是在合理程度上——请尝试阅读我关于这个主题的文章:http://blogs.msdn.com/b/ericlippert/archive/tags/memory+management/。特别是,“堆栈是实现细节”和“值类型的真相”将会对您有所帮助。 - Eric Lippert
1
@ebb:没错。我从来没有做过太多的汇编,但是弄清楚这个并不需要太多的汇编知识。这基本上是你学到的第一件事情。例如,如果你在x86上有一个名为AX的16位寄存器,你可以通过寄存器名称AH访问它的高字节,通过寄存器名称AL访问它的低字节。如果你需要一个32位寄存器,你可以将其命名为EAX。如果你需要一个64位寄存器,你可以尝试将其命名为RAX。RAX的低32位是EAX。EAX的低16位是AX。请查看这里的结构部分:http://en.wikipedia.org/wiki/X86 - Greg D
显示剩余23条评论
4个回答

8
答案是“它取决于情况。”有一点是确定的:本地变量 x 的存储(如果存在)包含一个实际值而不是引用,因为 int 是值类型。在简单直白的翻译中,x 的实际值(而不是指向它的指针或引用)存储在当前调用的激活记录中的一个本地变量中。生成的 IL 代码可能类似于这样:
  .maxstack 1
  .locals init (int32 V_0)

  ldc.i4.5  // push 5 on the evaluation stack
  stloc.0   // pop it into x

  ldloc.0   // now push a copy of x to pass to ...
  call void [mscorlib]System.Console::WriteLine(int32)

  ret       // return

请注意,.maxstack 指令指的是 IL 评估堆栈,不一定对应本机堆栈。如果浅层次的评估堆栈通常会被 JIT 编译代码保存在寄存器中。
在当前大多数 CLR 实现中,激活记录及其本地变量将存储在本机堆栈上。因此,天真地说,x 的值可能被认为是存储在堆栈上。
在上述代码的优化编译(或优化 JIT 翻译)中,IL 中可能根本不存在本地变量存储。毕竟,为什么要分配本地变量存储来存储它,并浪费两个指令来处理该本地变量,当您再也不需要它时?它可以作为一个常量参数内联传递到 WriteLine 中。
  .maxstack 1
  // Just pass 5 to WriteLine.  No local variables here.
  ldc.i4.5
  call void [mscorlib]System.Console::WriteLine(int32)
  // Bye.
  ret

可能还存在更多的问题。如果该函数声明为async(如C# 5 CTP中),或者x在函数的其他位置被lambda表达式捕获,或者该函数是使用yield return语句构建的枚举器,那么x的存储可能最终被强制转移到堆上:它将成为编译器生成类中的一个字段,以便变量的存储可以在闭包或继续逃逸本地范围的情况下生存堆栈帧的拆卸。


0

它存储了一个实际值x。没有地址或其他东西。


0

我认为这行代码被翻译为ldind.i命令。规范说明如下:

4D ldind.i 将地址addr处的本机int值作为本机int加载到堆栈上。


2
这段代码不可能生成 ldind.i,因为这是在不安全的代码中用于取消引用 int * 的操作。 - Jeffrey Hantin

0

很可能,由于int的字节大小会小于或等于您处理器的寄存器大小,因此很难预测CLR会选择做出其他决策的情况。

但是,尽管这可能是学术上感兴趣的事情,但在使用C#编写程序时不需要考虑它。 这只是一个实现细节,它可能会改变,代码仍然可以工作。 CLR实现者已经解放了我们的责任。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接