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

时间:2008-08-24 03:37:47

标签: .net memory

public class MyClass
{
    public int Age;
    public int ID;
}

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

据我了解,以下情况属实:

  1. 当MyMethod()退出时,引用m存在于堆栈中并超出范围。
  2. 值类型newID存在于堆栈中,并在MyMethod()退出时超出范围。
  3. 当MyMethod()退出时,由new运算符创建的对象存在于堆中并由GC回收,假设没有对该对象的其他引用存在。
  4. 这是我的问题:

    1. 对象中的值类型是否存在于堆栈或堆上?
    2. 对象中的装箱/取消装箱值类型是否值得关注?
    3. 这个主题是否有任何详细但可理解的资源?
    4. 从逻辑上讲,我认为类中的值类型会在堆中,但我不确定是否必须将它们装箱才能到达那里。

      编辑:

      有关此主题的建议阅读材料:

      1. CLR Via C# by Jeffrey Richter
      2. Essential .NET by Don Box

6 个答案:

答案 0 :(得分:9)

的值类型值与托管堆中的对象实例一起生成。方法的线程堆栈仅在方法的持续时间内存在;如果值仅存在于该堆栈中,该值如何保持不变?

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

以一个带有单个字段的简单类为例。

public class EmbeddedValues
{
  public int NumberField;
}

用它,一个简单的测试类。

public class EmbeddedTest
{
  public void TestEmbeddedValues()
  {
    EmbeddedValues valueContainer = new EmbeddedValues();

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

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

.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被告知 stfld 堆栈中加载的值“20”到加载的EmbeddValues的NumberField字段位置,直接进入托管堆。类似地,在检索值时,它使用 ldfld 指令将该托管堆位置的值直接复制到线程堆栈中。这些类型的操作不会发生框/拆箱。

答案 1 :(得分:2)

  1. 对象拥有的任何引用或值类型。
  2. 仅当您向对象投射时。

答案 2 :(得分:2)

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

答案 3 :(得分:2)

  • 答案1:堆。从他出色的'Essential .Net Vol 1'中删除Don Box
  

引用类型(RT)始终生成在堆上分配的实例。 相反,值类型(VT)取决于上下文 - 如果本地var是VT,则CLR在堆栈上分配内存。如果类中的字段是VT的成员,则CLR为实例分配内存,作为声明字段的对象/类型布局的一部分。

  • 答案#2:不会。只有通过对象参考/接口指针访问结构时才会发生拳击。 obInstance.VT_typedfield 不会包装。

      

    RT变量包含它引用的对象的地址。 2 RT var可以指向同一个对象。 相反,VT变量本身就是实例。 2 VT var不能指向同一个对象(struct)

  • 答案3:Don Box的Essential .net / Jeffrey Richter通过C#的CLR。我有一份前者的副本......虽然后者可能会更新.Net修订

答案 4 :(得分:1)

  

对象中的值类型是否存在于堆栈或堆上?

在堆上。它们是对象足迹分配的一部分,就像保持引用的指针一样。

  

对象中的装箱/取消装箱值类型是否值得关注?

这里没有拳击。

  

这个主题是否有任何详细但可理解的资源?

给Richter的书+1票。

答案 5 :(得分:0)

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

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

声明Foo bar;将导致bar.xbar.ybar.z存储在bar将要存储的任何位置。从存储布局的角度来看,将bar这样的声明添加到类中将等同于添加三个int字段。实际上,如果除了访问其字段之外从未对bar执行任何操作,bar的字段的行为与三个字段bar_xbar_y和{{1 [访问最后一个将需要使用bar_cantaccessthis_z而不是访问其字段,但无论它是否实际用于任何事物都会占用空间。

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