3

在 .Net 中,整数是值类型,这意味着它存储在堆栈中。整数也是类(通常是 System.Int32)。它们有CompareTo、Equals、...等方法。因此,它们应该在堆栈上占用超过四个字节。然而,下面的示例显示它们恰好占用 4 个字节:

unsafe static void Main()
{
    int a = 2, b = 4;
    Console.WriteLine("Adress of a : {0}", (int)&a);
    Console.WriteLine("Adress of b : {0}", (int)&b);
    Console.WriteLine("Size of integer: {0}", (int)(&a) - (int)(&b));
}

输出:

Adress of a : 1372876
Adress of b : 1372872
Size of integer: 4

CLR 是否对整数和其他值类型(float、long、double、...)进行了特殊处理?

4

4 回答 4

15

不,它们是值类型这一事实并不意味着它们存储在堆栈中。这意味着它们存储在变量所在的任何地方

但是,嘿,让我们继续使用局部变量业务,此时(没有捕获等)它们确实存在于堆栈中。它们占用 4 个字节。为什么他们会拿更多?堆栈上不需要 vtable,因为元数据已经指定了类型:对于将调用哪些虚拟方法等没有歧义。

编辑:正如肖恩在评论中指出的那样(但我想让它更明显),System.Int32是一个结构,而不是一个类。(事实上​​,CLR 将创建一个影子引用类型来覆盖 int 的装箱值,但这是另一回事。)

于 2009-03-01T21:52:42.230 回答
5

因此,它们应该在堆栈上占用四个以上的字节。

这不遵循。编译器运行时知道确切的类型。值类型不能进一步子类型化,因此不需要“vtable”或其他特定于对象的动态调度机制。

当值类型被装箱以将它们放在堆上时,需要正常的 .NET Object 标头。

于 2009-03-01T21:52:37.657 回答
4

如果值类型是方法中的局部变量,则在堆栈上分配值类型。如果一个值类型是一个类的成员,它将作为对象在堆上的内存区域的一部分进行分配。

值类型变量不需要任何额外的数据来跟踪类型,就像引用类型一样。编译器总是知道值类型变量在哪里以及它们的类型是什么,因此除了实际数据之外不需要额外的数据。Int32 变量总是四个字节。

在堆上分配了一个引用类型,并且它有一个(或更多)指向它的引用。引用本身实际上是一个值类型,所以它只是一个指针,编译器会跟踪它在哪里以及它是什么类型。引用的类型不必与它所指向的对象的类型相同,因此对象需要额外的信息来跟踪类型。例如,指向 StringBuilder 类实例的对象引用:

object o = new StringBuilder();

在这里,编译器跟踪引用的类型是对象,因此它只是一个指针(在 32 位应用程序中为 4 个字节)。StringBuilder 对象存储在堆上,它有两个额外的指针来跟踪实际类型。

值类型也可以被装箱,即作为对象存储在堆上。当您将值类型转换为 Object 时会发生这种情况:

object p = 42;

这将在堆上分配一个对象并将整数的值复制到其中。该对象将需要额外的类型信息来跟踪类型,因此它将在堆上使用 12 个字节而不是 4 个字节(在 32 位应用程序中)。

于 2009-03-01T22:17:05.323 回答
0

类型定义和为该类型的实例存储的值之间存在差异,例如......

// type definition
public class Bla {}
// instance of type bla
public Bla myBla = new Bla();

本质上,一个 int 的大小就像它看起来的那样(4 个字节),但正如您所知道的,这就是它需要声明的内存空间的大小。

类型定义存储在其他地方,像 CompareTo 这样的方法仅以这种方式声明一次,而不是为您声明的该类型的每个实例声明一次,并且由于它们是作为框架库本身的一部分加载的,因此对于您的应用程序而言,这些定义有效地占用0空间。

于 2011-05-20T12:47:43.053 回答