【问题标题】:.Net/C# : what's the real size of an integer?.Net/C#:整数的实际大小是多少?
【发布时间】:2023-04-02 21:40:01
【问题描述】:

在 .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、...)进行特殊处理?

【问题讨论】:

  • 我认为 int32 是一个结构,而不是一个类。
  • 尝试在 64 位操作系统上运行它...
  • 即使这样,平台仍将整数保留为 4 个字节。 native int 那是另一回事。虽然我承认这个坏例子可能会产生不同的价值
  • Mathieu 计算大小的方法非常脆弱。改用 sizeof() 。 msdn.microsoft.com/en-us/library/eahchzkf.aspx
  • “因此,它们应该在堆栈上占用超过四个字节”为什么?您需要存储哪些元数据,因为它是一个堆栈变量?

标签: c# integer


【解决方案1】:

不,它们是值类型这一事实并不意味着它们存储在堆栈中。这意味着它们存储在wherever the variable lives

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

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

【讨论】:

  • 如果编译器知道调用方法的变量的类型并且所述方法是在该类上直接定义的(如果是虚拟的,那么必须有覆盖类/结构),那么 IL 中描述的调用包含所有必需的信息,因此不需要装箱
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

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

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

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

    object o = new StringBuilder();
    

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

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

    object p = 42;
    

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

    【讨论】:

    • 对于您的 StringBuilder 示例,两个额外的指针是引用的一部分还是对象的一部分?为什么是两个而不是一个?谢谢。
    • 不,这八个字节不包括引用,它们是堆上对象的一部分。一个指向虚拟方法表的指针,以及另外四个没有很好记录的字节......这里有更多信息:stackoverflow.com/questions/489805/…
    • 只是为了挑剔:“如果它是方法中的局部变量”并且该变量没有被捕获到闭包中,并且该方法不是迭代器块;-p
    【解决方案4】:

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

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多