一尘不染

数组,堆和堆栈以及值类型

c#

int[] myIntegers;
myIntegers = new int[100];

在上面的代码中,是否是new int
[100]在堆上生成数组?根据我通过c#进行的CLR阅读,答案是肯定的。但是我不明白的是,数组内部的实际int发生了什么。因为它们是值类型,所以我猜想它们必须装箱,例如,我可以将myIntegers传递给程序的其他部分,如果一直将它们留在程序中,则会使堆栈混乱。还是我错了?我猜想它们会被装箱,并且只要数组存在就可以在堆中生存。


阅读 291

收藏
2020-05-19

共1个答案

一尘不染

您的数组分配在堆上,并且没有将int装箱。

造成混淆的原因很可能是因为人们曾说过,引用类型是在堆上分配的,而值类型是在堆栈上分配的。这不是一个完全准确的表示。

所有局部变量和参数都分配在堆栈上。这包括值类型和引用类型。两者之间的区别仅在于变量中 存储 的内容。毫不奇怪,对于值类型,该类型的
直接存储在变量中,对于引用类型,该类型的值存储在堆中,对此值的 引用 就是存储在变量中的值。

字段也是如此。当为聚合类型(a class或a
struct)的实例分配内存时,它必须包括其每个实例字段的存储。对于引用类型的字段,此存储仅保存对该值的引用,该引用本身将在以后分配给堆。对于值类型字段,此存储保存实际值。

因此,给定以下类型:

class RefType{
    public int    I;
    public string S;
    public long   L;
}

struct ValType{
    public int    I;
    public string S;
    public long   L;
}

这些类型的每一个的值都将需要16个字节的内存(假设一个32位字长)。字段I在每种情况下需要4个字节来存储其值,则场S需要4个字节来存储其引用,字段L需要8个字节存储其值。因此,对于两者的价值存储RefTypeValType看起来像这样:

 0┌──────────────┐
   │我│
 4├───────────────┤
   │S│
 8├──────────────┤
   │L│
   ││
16└──────────────┘

现在,如果你在一个函数中有三个局部变量,类型RefTypeValTypeint[],就像这样:

RefType refType;
ValType valType;
int[]   intArray;

那么您的堆栈可能看起来像这样:

 0┌──────────────┐
   │refType│
 4├───────────────┤
   │valType│
   ││
   ││
   ││
20├──────────────┤
   │intArray│
24└──────────────┘

如果您为这些局部变量分配了值,如下所示:

refType = new RefType();
refType.I = 100;
refType.S = "refType.S";
refType.L = 0x0123456789ABCDEF;

valType = new ValType();
valType.I = 200;
valType.S = "valType.S";
valType.L = 0x0011223344556677;

intArray = new int[4];
intArray[0] = 300;
intArray[1] = 301;
intArray[2] = 302;
intArray[3] = 303;

然后您的堆栈可能看起来像这样:

 0┌──────────────┐
   │0x4A963B68│-`refType`的堆地址
 4├───────────────┤
   │200│-valType.I的值
   │0x4A984C10│-`valType.S`的堆地址
   │0x44556677│-`valType.L`的低32位
   │0x00112233│-`valType.L`的高32位
20├──────────────┤
   │0x4AA4C288│-`intArray`的堆地址
24└──────────────┘

地址处的内存0x4A963B68(值为refType)将类似于:

 0┌──────────────┐
   │100│-`refType.I`的值
 4├───────────────┤
   │0x4A984D88│-`refType.S`的堆地址
 8├──────────────┤
   │0x89ABCDEF│-`refType.L`的低32位
   │0x01234567│-`refType.L`的高32位
16└──────────────┘

地址处的内存0x4AA4C288(值为intArray)将类似于:

 0┌──────────────┐
   │4│-数组长度
 4├───────────────┤
   │300│-`intArray [0]`
 8├──────────────┤
   │301│-`intArray [1]`
12├──────────────┤
   │302│-`intArray [2]`
16├─────────────┤
   │303│-`intArray [3]`
20└──────────────┘

现在,如果您传递intArray给另一个函数,则压入堆栈的值将是0x4AA4C288数组的地址, 而不是 数组的副本。

2020-05-19