int[] myIntegers;
myIntegers = new int[100];
在上面的代码,是新的INT [100]生成在堆上阵列? 从我上的CLR通过C#阅读,答案是肯定的。 但我不明白,是发生了什么实际的INT的数组里面。 由于他们是值类型,我猜他们不得不装箱,因为我可以,例如,通过myIntegers到程序的其他部分,它会弄乱堆栈,如果他们在这剩下的所有时间。 还是我错了? 我猜他们刚刚被装箱,并会在堆中生活,只要阵列存在。
int[] myIntegers;
myIntegers = new int[100];
在上面的代码,是新的INT [100]生成在堆上阵列? 从我上的CLR通过C#阅读,答案是肯定的。 但我不明白,是发生了什么实际的INT的数组里面。 由于他们是值类型,我猜他们不得不装箱,因为我可以,例如,通过myIntegers到程序的其他部分,它会弄乱堆栈,如果他们在这剩下的所有时间。 还是我错了? 我猜他们刚刚被装箱,并会在堆中生活,只要阵列存在。
你的数组在堆中分配,以及整数不装箱。
因为人说,引用类型在堆上分配值类型都是在栈上分配的问题的根源是可能的。 这不是一个完全精确的表示。
所有的局部变量和参数被分配在栈上。 这既包括值类型和引用类型。 两者之间的区别只是什么是存储在变量。 不出所料,对于值类型,该类型的值被直接存储在变量,和用于参考的类型,该类型的值被存储在堆上,并且该值的基准是什么被存储在变量中。
这同样适用于域如此。 当存储器被分配用于一个聚合类型(类或结构)的一个实例,它必须包括它的每个实例字段的存储。 参考型的字段,该存储保持刚刚到值,这将本身在堆上分配以后的参考。 对于价值型的字段,这个存储保存实际值。
因此,考虑到以下几种类型:
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个字节存储其值。 因此,对于这两个值的内存RefType
和ValType
看起来是这样的:
0 ┌───────────────────┐ │ I │ 4 ├───────────────────┤ │ S │ 8 ├───────────────────┤ │ L │ │ │ 16 └───────────────────┘
现在,如果你有一个函数三个局部变量的类型RefType
, ValType
和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 └───────────────────┘
内存地址0x4A963B68(价值refType
)会是这样的:
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 └───────────────────┘
内存地址0x4AA4C288(价值intArray
)会是这样的:
0 ┌───────────────────┐ │ 4 │ -- length of array 4 ├───────────────────┤ │ 300 │ -- `intArray[0]` 8 ├───────────────────┤ │ 301 │ -- `intArray[1]` 12 ├───────────────────┤ │ 302 │ -- `intArray[2]` 16 ├───────────────────┤ │ 303 │ -- `intArray[3]` 20 └───────────────────┘
现在,如果你通过intArray
到另一个函数,该值压入堆栈将0x4AA4C288,数组的地址,而不是一个数组的副本。
是的阵列将被位于堆。
阵列内的整数不会被装箱。 仅仅因为一个值类型在堆中存在,并不一定意味着它会被装箱。 当值类型,诸如整型,被分配给对象的类型的引用拳击才会发生。
例如
不框:
int i = 42;
myIntegers[0] = 42;
盒:
object i = 42;
object[] arr = new object[10]; // no boxing here
arr[0] = 42;
你也可以想看看埃里克的帖子关于这个问题:
要了解发生了什么,这里有一些事实:
所以,如果你有一个整数数组,数组被分配在堆上,它包含在堆上数组对象的一部分的整数。 整数驻留在阵列对象内部在堆中,而不是作为单独的对象,以便它们不装箱。
如果你有一个字符串数组,它是真正的字符串引用数组。 作为参考是值类型他们将在堆阵列对象的一部分。 如果你把一个字符串对象数组中,你居然把参考字符串对象数组中,字符串是在堆一个单独的对象。
我觉得你的问题的核心是有关参考和值类型误解。 这事可能每个.NET和Java开发人员与挣扎。
数组仅仅是一个值的列表。 如果它是一个引用类型的阵列(比如一个string[]
则阵列对各种参考文献的列表string
在堆中的对象,作为基准为引用类型的值 。 在内部,这些引用被实现为指针在内存中的地址。 如果您希望可视此,这样的排列是这样的内存(在堆):
[ 00000000, 00000000, 00000000, F8AB56AA ]
这是一个数组string
包含4个引用string
在堆上的对象(这里的数字是十六进制)。 目前,只有最后一个string
实际指向任何东西(分配内存时被初始化为全零),此阵将基本上是这样的代码在C#中的结果:
string[] strings = new string[4];
strings[3] = "something"; // the string was allocated at 0xF8AB56AA by the CLR
上述阵列将是在32位的程序。 在64位程序,引用是两倍大( F8AB56AA
将00000000F8AB56AA
)。
如果有值类型的数组(说的int[]
则该数组是整数列表,作为值类型的值 是值本身(因此而得名)。 这种阵列的可视化将是这样的:
[ 00000000, 45FF32BB, 00000000, 00000000 ]
这是4个整数,其中只有第二INT被分配的值的阵列(至1174352571,它是十六进制数的十进制表示)和整数的其余部分将是0(如我说,存储器被初始化为零和00000000十六进制为0十进制)。 生成此数组的代码将是:
int[] integers = new int[4];
integers[1] = 1174352571; // integers[1] = 0x45FF32BB would be valid too
此int[]
阵列也将被保存在堆上。
作为另一示例,一个的存储器short[4]
阵列看起来像这样:
[ 0000, 0000, 0000, 0000 ]
作为a的值 short
是一个2字节数。
当值类型存储,只是一个实现细节作为埃里克利珀解释得非常好这里 ,没有内在价值和引用类型(这是在行为上的差异)之间的差异。
当你传递的东西的方法(是引用类型或值类型),那么该类型的值的副本实际上是传递给方法。 在引用类型的情况下,该值是一个参考值(认为这是一个指向一块内存,尽管这也是一个实施细节),并在值型的情况下,该值是事物本身。
// 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){}
拳击只有当你将一个值类型为引用类型发生。 此代码框:
object o = 5;
整数数组被分配在堆上,仅此而已,无所不及。 myIntegers到的整数分配部分的开始引用。 该参考位于堆栈上。
如果你有一个数组引用对象的类型,如对象类型,myObjects [],位于堆栈上,将参考束哪个参考对象themselfes值。
总之,如果你通过myIntegers一些功能,你只能通过参考到整数的真正一堆被分配的地方。
有一个在你的示例代码没有任何拳击。
值类型可以住在堆,因为他们的整数数组中做。 该阵列在堆上分配,它可储存整数,这恰好是值类型。 所述阵列的内容被初始化为默认(INT),这恰好是零。
考虑包含的值类型的类:
class HasAnInt
{
int i;
}
HasAnInt h = new HasAnInt();
变量h是指住在堆HasAnInt的一个实例。 这恰好包含一个值类型。 这完全没关系,“我”只是碰巧,因为它是包含在一个班住在堆。 有一个在这个例子中,没有任何拳击。
够了一直被大家说,但是如果有人正在寻找一个明确的(但非官方)样品和有关堆,栈,局部变量文档和静态变量,是指在完全乔恩斯基特的文章在.NET内存-善有善报哪里
摘抄:
每个局部变量(即,一个在方法中声明)被存储在堆栈中。 这包括引用类型变量 - 变量本身是在堆栈上,但要记住,引用类型变量的值仅是一个参考(或空),没有对象本身。 方法参数当作局部变量太多,但如果它们与裁判修饰符声明,他们没有得到自己的插槽,但共享调用代码使用的变量插槽。 看到我的传球更多细节参数文章。
为引用类型的实例变量总是在堆上。 这就是目标本身“生活”。
为一个值类型的实例变量被存储在相同的上下文中声明值类型的变量中。 该实例的存储器槽有效地包含用于实例中的每个字段的时隙。 这意味着(给定前面的2个点),方法中的声明的结构变量将总是在堆栈中,而结构体变量这是一个类的实例字段将是在堆上。
每个静态变量被存储在堆上,无论它是否是一个参考类型或值类型中声明。 只有一个总插槽不管如何创建多个实例。 (这里并不需要创建因为尽管存在一个插槽的实例)的到底是哪堆变量生活是复杂的细节,但在细节上关于这个问题的MSDN文章中解释。
这些插图由@P爸爸上面的回答描绘
我在说明我的风格相应内容。