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

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

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

  1. 引用 m 存在于堆栈中,并且在 MyMethod() 退出时超出范围。
  2. 值类型 newID 存在于堆栈中,并在 MyMethod() 退出时超出范围。
  3. 由 new 运算符创建的对象存在于堆中,并在 MyMethod() 退出时由 GC 回收,假设不存在对该对象的其他引用。

这是我的问题:

  1. 对象中的值类型是存在于栈上还是堆上?
  2. 对象中的装箱/拆箱值类型是否值得关注?
  3. 是否有关于此主题的详细但易于理解的资源?

从逻辑上讲,我认为类中的值类型会在堆中,但我不确定它们是否必须装箱才能到达那里。

编辑:

本主题推荐阅读:

  1. CLR 通过 C# 由 Jeffrey Richter
  2. Don Box 的 Essential .NET
4

6 回答 6

9

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

托管堆中类的对象大小是其值类型字段、引用类型指针和其他 CLR 开销变量(如同步块索引)的总和。当为对象的值类型字段赋值时,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 Disassembler 查看 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 被告知将堆栈中加载的“20”值直接存储到托管堆中加载的 EmbeddValues 的 NumberField 字段位置同样,在检索值时,它使用ldfld指令直接将值从托管堆位置复制到线程堆栈中。这些类型的操作不会发生装箱/拆箱。

于 2008-08-24T06:11:55.997 回答
2
  1. 对象拥有的任何引用或值类型都存在于堆中。
  2. 仅当您将整数转换为对象时。
于 2008-08-24T03:52:14.523 回答
2

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

于 2008-08-24T04:58:41.900 回答
2
  • 答案#1:堆。从他出色的“Essential .Net Vol 1”中解释 Don Box

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

  • Ans#2:不会。只有当您通过对象引用/接口指针访问结构时才会发生装箱。obInstance.VT_typedfield不会装箱。

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

  • Ans#3: Don Box's Essential .net / Jeffrey Richter's CLR via C#。我有前者的副本……尽管后者可能会针对 .Net 修订进行更多更新

于 2008-08-24T07:43:38.180 回答
1

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

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

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

这里没有拳击。

是否有关于此主题的详细但易于理解的资源?

为里希特的书+1 票。

于 2008-08-24T05:55:30.937 回答
0

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

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

声明Foo bar;将导致bar.x,bar.ybar.z存储在要存储的任何bar位置。从存储布局的角度来看,将这样的声明添加bar到类中将等效于添加三个int字段。实际上,如果一个人从未对barexcept 访问其字段进行任何操作,则字段的bar行为将与三个字段bar_xbar_ybar_cantaccessthis_z[ 访问最后一个字段的行为相同,而bar不是访问其字段,但它会占用空间或者它实际上没有用于任何事情]。

Recognizing structure-type storage locations as being aggregations of fields is the first step to understanding structures. Trying to view them as holding some kind of object might seem "simpler", but doesn't match how they actually work.

于 2014-01-27T20:20:46.070 回答