.net托管内存如何处理对象内部的值类型?

.net托管内存如何处理对象内部的值类型?

How does .net managed memory handle value types inside objects?

1
2
3
4
5
6
7
8
9
10
11
public class MyClass
{
    public int Age;
    public int ID;
}

public void MyMethod()
{
    MyClass m = new MyClass();
    int newID;
}

据我了解,以下是正确的:

  • 引用m位于堆栈上,并且在MyMethod()退出时超出范围。
  • 值类型newID驻留在堆栈上,并在MyMethod()退出时超出范围。
  • 假设不存在对该对象的其他引用,则由新运算符创建的对象将驻留在堆中,并在MyMethod()退出时被GC回收。
  • 这是我的问题:

  • 对象中的值类型是否存在于堆栈或堆中?
  • 装箱/拆箱对象中的值类型是否值得关注?
  • 是否有关于此主题的详细但可理解的资源?
  • 从逻辑上讲,我认为类内的值类型应该在堆中,但是我不确定是否必须将它们装箱才能到达那里。

    编辑:

    建议阅读该主题:

  • Jeffrey Richter编写的CLR通过C#
  • Don Box的Essential .NET

  • 类的值类型值必须与对象实例一起存在于托管堆中。方法的线程堆栈仅在方法持续时间内有效。如果值仅存在于该堆栈中,该值如何持久?

    托管堆中类的对象大小是其值类型字段,引用类型指针和其他CLR开销变量(例如,同步块索引)的总和。当将一个值分配给对象的value-type字段时,CLR将该值复制到该对象内为该特定字段分配的空间。

    例如,一个具有单个字段的简单类。

    1
    2
    3
    4
    public class EmbeddedValues
    {
      public int NumberField;
    }

    并带有一个简单的测试类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class EmbeddedTest
    {
      public void TestEmbeddedValues()
      {
        EmbeddedValues valueContainer = new EmbeddedValues();

        valueContainer.NumberField = 20;
        int publicField = valueContainer.NumberField;
      }
    }

    如果您使用.NET Framework SDK提供的MSIL反汇编程序来查看EmbeddedTest.TestEmbeddedValues()的IL代码,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    .method public hidebysig instance void  TestEmbeddedValues() cil managed
    {
      // Code size       23 (0x17)
      .maxstack  2
      .locals init ([0] class soapextensions.EmbeddedValues valueContainer,
               [1] int32 publicField)
      IL_0000:  nop
      IL_0001:  newobj     instance void soapextensions.EmbeddedValues::.ctor()
      IL_0006:  stloc.0
      IL_0007:  ldloc.0
      IL_0008:  ldc.i4.s   20
      IL_000a:  stfld      int32 soapextensions.EmbeddedValues::NumberField
      IL_000f:  ldloc.0
      IL_0010:  ldfld      int32 soapextensions.EmbeddedValues::NumberField
      IL_0015:  stloc.1
      IL_0016:  ret
    } // end of method EmbeddedTest::TestEmbeddedValues

    请注意,CLR被告知将堆栈中的装入值" 20" stfld装入装入的EmbeddValues的NumberField字段位置,直接放入托管堆中。同样,在检索值时,它使用ldfld指令将值直接从该托管堆位置复制到线程堆栈中。这些类型的操作不会进行装箱/拆箱。


    • 答案1:堆。从Don Box出色的《 Essential .Net Vol 1》中释义

    Reference Types(RT) always yield instances that are allocated on the heap. In contrast, value types(VT) are dependent on the context - If a local var is a VT, the CLR allocates memory on the stack. If a field in a class is a member of a VT, then the CLR allocates memory for the instance as part of the layout of the object/Type in which field is declared.

    • 答案2:否。仅当您通过对象引用/接口指针访问结构时,才会进行装箱。 obInstance.VT_typedfield不会装箱。

      RT variables contains the address of the object it refers to. 2 RT var can point to the same object. In contrast, VT variables are the instances themselves. 2 VT var cannot point to same object(struct)

    • 答案3:Don Box的Essential .net /杰弗里·里希特(Jeffrey Richter)通过C#进行的CLR。我有前者的副本...尽管以后可能会针对.Net修订版进行更多更新


    我所见过的最好的资源是杰弗里·里希特(Jeffrey Richter)通过C#编写的CLR书。如果您进行任何.NET开发,都非常值得一读。基于该文本,我的理解是引用类型内的值类型确实存在于父对象中嵌入的堆中。引用类型总是在堆上。
    装箱和拆箱不是对称的。与拆箱相比,装箱可能是更大的问题。装箱将需要将值类型的内容从堆栈复制到堆。根据发生这种情况的频率,可能没有必要使用结构而不是类。如果您有一些对性能有严格要求的代码,并且不确定装箱和拆箱是否正在发生,请使用工具来检查方法的IL代码。您将在IL中看到单词框和框框。就个人而言,我将评估代码的性能,然后再查看是否值得担心。就您而言,我认为这不是一个关键问题。每次访问引用类型内的该值类型时,您都不必从堆栈复制到堆(盒子)。在这种情况下,拳击成为更有意义的问题。


  • 对象拥有的所有引用或值类型都存在于堆中。
  • 仅当您将int强制转换为Objects时。

  • Do value types within objects live on the stack or the heap?

    在堆上。它们是对象覆盖区分配的一部分,就像保存引用的指针一样。

    Is boxing/unboxing value types in an object a concern?

    这里没有拳击。

    Are there any detailed, yet understandable, resources on this topic?

    对Richter的书+1票。


    结构类型的变量或其他存储位置是该类型的公共和私有实例字段的集合。 给定

    1
    struct Foo {public int x,y; int z;}

    声明Foo bar;将导致bar.xbar.ybar.z被存储在要存储bar的位置。 从存储布局的角度来看,在类中添加这样的bar声明等同于添加三个int字段。 的确,如果一个人除了访问其字段以外从未对bar做任何事情,那么bar的字段的行为将与三个字段bar_xbar_ybar_cantaccessthis_z相同[访问最后一个字段将需要执行 bar而不是访问其字段,但这会占用空间,无论它是否实际用于任何事物]。

    将结构类型的存储位置识别为字段的聚集是了解结构的第一步。 试图将它们视为持有某种对象可能看起来"简单",但与它们的实际工作方式不匹配。


    推荐阅读