C# 数组、堆、堆栈和值类型

C# 数组、堆、堆栈和值类型,c#,arrays,memory,stack,heap,C#,Arrays,Memory,Stack,Heap,在上面的代码中,new int[100]是否在堆上生成数组?从我通过c#在CLR上读到的内容来看,答案是肯定的。但我不明白的是,数组中实际的int发生了什么。由于它们是值类型,我猜它们必须被装箱,例如,我可以将MyInteger传递给程序的其他部分,如果它们一直留在堆栈上,那么堆栈就会变得杂乱无章。还是我错了?我猜它们只是被装箱了,只要数组存在,它们就会在堆上生存。是的,数组将位于堆上 数组中的整数将不会装箱。仅仅因为堆上存在值类型,并不一定意味着它将被装箱。只有将值类型(如int)指定给obj

在上面的代码中,new int[100]是否在堆上生成数组?从我通过c#在CLR上读到的内容来看,答案是肯定的。但我不明白的是,数组中实际的int发生了什么。由于它们是值类型,我猜它们必须被装箱,例如,我可以将MyInteger传递给程序的其他部分,如果它们一直留在堆栈上,那么堆栈就会变得杂乱无章。还是我错了?我猜它们只是被装箱了,只要数组存在,它们就会在堆上生存。

是的,数组将位于堆上

数组中的整数将不会装箱。仅仅因为堆上存在值类型,并不一定意味着它将被装箱。只有将值类型(如int)指定给object类型的引用时,才会发生装箱

比如说

不包含以下框:

int[] myIntegers;
myIntegers = new int[100];
方框:

int i = 42;
myIntegers[0] = 42;
您还可以查看Eric关于此主题的帖子:


在堆上分配整数数组,不多也不少。myIntegers引用分配INT的节的开头。该引用位于堆栈上

如果您有一个引用类型对象数组,比如位于堆栈上的对象类型myObjects[],则会引用引用对象本身的一组值


总之,如果将MyInteger传递给某些函数,则只传递对实际整数组分配位置的引用。

示例代码中没有装箱

值类型可以像在int数组中一样存在于堆中。数组是在堆上分配的,它存储int,而int恰好是值类型。数组的内容被初始化为默认值(int),该值恰好为零

考虑一个包含值类型的类:

object i = 42;
object[] arr = new object[10];  // no boxing here 
arr[0] = 42;

变量h指的是堆上的hasaint实例。它恰好包含一个值类型。这完全没关系,'我'正好住在堆上,因为它包含在一个类中。本例中也没有装箱。

为了了解发生了什么,以下是一些事实:

  • 对象始终在堆上分配
  • 堆只包含对象
  • 值类型要么在堆栈上分配,要么在堆上分配对象的一部分
  • 数组是一个对象
  • 数组只能包含值类型
  • 对象引用是一种值类型
因此,如果您有一个整数数组,那么该数组在堆上分配,它包含的整数是堆上数组对象的一部分。整数驻留在堆上的数组对象内部,而不是作为单独的对象,因此它们不会被装箱


如果您有一个字符串数组,那么它实际上是一个字符串引用数组。由于引用是值类型,它们将是堆上数组对象的一部分。如果在数组中放入字符串对象,实际上就是在数组中放入对字符串对象的引用,并且字符串是堆上的一个单独对象。

我认为您问题的核心在于对引用和值类型的误解。这可能是每个.NET和Java开发人员都在努力解决的问题

数组只是一个值列表。如果它是引用类型的数组(例如
string[]
),那么该数组就是堆上各种
string
对象的引用列表,因为引用是引用类型的值。在内部,这些引用被实现为指向内存中地址的指针。如果希望将其可视化,这样的数组在内存中(在堆上)将如下所示:

[00000000,00000000,00000000,F8AB56AA]

这是一个
string
数组,其中包含对堆上
string
对象的4个引用(这里的数字是十六进制的)。目前,只有最后一个
字符串实际指向任何东西(内存在分配时初始化为所有零),此数组基本上是C#中此代码的结果:

上面的数组将在一个32位程序中。在64位程序中,引用将是两倍大(
F8AB56AA
将是
00000000 F8AB56AA

如果您有一个值类型数组(比如一个
int[]
),那么该数组就是一个整数列表,因为值类型的值就是值本身(因此而得名)。这种阵列的可视化如下:

[00000000,45FF32BB,00000000,00000000]

这是一个由4个整数组成的数组,其中只有第二个整数被赋值(到1174352571,这是该十六进制数的十进制表示),其余整数将为0(如我所说,内存初始化为零,十六进制的00000000是十进制的0)。生成此数组的代码为:

string[] strings = new string[4];
strings[3] = "something"; // the string was allocated at 0xF8AB56AA by the CLR
这个
int[]
数组也将存储在堆上

另一个例子是,
short[4]
数组的内存如下所示:

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

struct ValType{
    public int    I;
    public string S;
    public long   L;
}
0 ┌───────────────────┐ │ I │ 4 ├───────────────────┤ │ S │ 8 ├───────────────────┤ │ L │ │ │ 16 └───────────────────┘
RefType refType;
ValType valType;
int[]   intArray;
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;
[0000,0000,0000,0000]

因为
short
的值是一个2字节的数字

存储值类型的位置只是一个实现细节,正如Eric Lippert很好地解释的那样,不是值类型和引用类型之间固有的差异(即行为上的差异)

当您将某个内容传递给某个方法(引用类型或值类型)时,该类型的值的副本实际上会传递给该方法。在引用类型的情况下,值是一个引用(将其视为指向一段内存的指针,尽管这也是一个实现细节),而在值类型的情况下,值就是对象本身

 int[] integers = new int[4];
 integers[1] = 1174352571; // integers[1] = 0x45FF32BB would be valid too
只有将值类型转换为引用类型时,才会发生装箱。此代码框:

// Calling this method creates a copy of the *reference* to the string
// and a copy of the int itself, so copies of the *values*
void SomeMethod(string s, int i){}

您的数组是在堆上分配的,INT没有装箱

你困惑的根源很可能是因为人们说
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 │ -- heap address of `refType` 4 ├───────────────────┤ │ 200 │ -- value of `valType.I` │ 0x4A984C10 │ -- heap address of `valType.S` │ 0x44556677 │ -- low 32-bits of `valType.L` │ 0x00112233 │ -- high 32-bits of `valType.L` 20 ├───────────────────┤ │ 0x4AA4C288 │ -- heap address of `intArray` 24 └───────────────────┘ 0 ┌───────────────────┐ │ 100 │ -- value of `refType.I` 4 ├───────────────────┤ │ 0x4A984D88 │ -- heap address of `refType.S` 8 ├───────────────────┤ │ 0x89ABCDEF │ -- low 32-bits of `refType.L` │ 0x01234567 │ -- high 32-bits of `refType.L` 16 └───────────────────┘ 0 ┌───────────────────┐ │ 4 │ -- length of array 4 ├───────────────────┤ │ 300 │ -- `intArray[0]` 8 ├───────────────────┤ │ 301 │ -- `intArray[1]` 12 ├───────────────────┤ │ 302 │ -- `intArray[2]` 16 ├───────────────────┤ │ 303 │ -- `intArray[3]` 20 └───────────────────┘