dev*_*ium 129 c# memory arrays heap stack
int[] myIntegers;
myIntegers = new int[100];
Run Code Online (Sandbox Code Playgroud)
在上面的代码中,是新的int [100]在堆上生成数组吗?从我通过c#读到的CLR,答案是肯定的.但我无法理解的是,数组中的实际int会发生什么.由于它们是值类型,我猜它们必须被装箱,因为我可以,例如,将myIntegers传递给程序的其他部分,如果它们一直留在堆栈上它会使堆栈混乱.还是我错了?我猜他们只是盒装,并且只要数组存在就会活在堆上.
P D*_*ddy 276
您的数组是在堆上分配的,而int不是盒装的.
您混淆的原因可能是因为人们已经说过在堆上分配了引用类型,并且在堆栈上分配了值类型.这不是一个完全准确的表示.
所有局部变量和参数都在堆栈上分配.这包括值类型和引用类型.两者之间的区别仅在于存储在变量中的内容.不出所料,对于一个值类型,则值的类型的直接存储在变量,和用于参考的类型,该类型的值被存储在堆上,和一个参考于该值是什么被存储在变量中.
对于字段也是如此.为聚合类型(类或结构)的实例分配内存时,它必须包含每个实例字段的存储.对于引用类型字段,此存储仅包含对该值的引用,该值本身稍后将在堆上分配.对于值类型字段,此存储保存实际值.
因此,给定以下类型:
class RefType{
public int I;
public string S;
public long L;
}
struct ValType{
public int I;
public string S;
public long L;
}
Run Code Online (Sandbox Code Playgroud)
每种类型的值都需要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;
Run Code Online (Sandbox Code Playgroud)
然后你的堆栈可能如下所示:
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;
Run Code Online (Sandbox Code Playgroud)
然后你的堆栈可能看起来像这样:
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,即数组的地址,而不是数组的副本.
Jar*_*Par 23
是的,数组将位于堆上.
数组中的int不会被装箱.仅仅因为堆上存在值类型,并不一定意味着它将被装箱.仅当将值类型(例如int)分配给object类型的引用时,才会发生Boxing.
例如
不包装盒:
int i = 42;
myIntegers[0] = 42;
Run Code Online (Sandbox Code Playgroud)
盒:
object i = 42;
object[] arr = new object[10]; // no boxing here
arr[0] = 42;
Run Code Online (Sandbox Code Playgroud)
您可能还想查看Eric关于此主题的帖子:
Guf*_*ffa 20
要了解发生了什么,这里有一些事实:
因此,如果您有一个整数数组,则该数组将在堆上分配,并且它包含的整数是堆上数组对象的一部分.整数驻留在堆上的数组对象内,而不是作为单独的对象,因此它们不会被装箱.
如果你有一个字符串数组,它实际上是一个字符串引用数组.由于引用是值类型,它们将成为堆上数组对象的一部分.如果在数组中放入一个字符串对象,实际上是将引用放入数组中的字符串对象,并且该字符串是堆上的单独对象.
我认为问题的核心在于对参考和价值类型的误解.这可能是每个.NET和Java开发人员都在努力解决的问题.
数组只是一个值列表.如果它是引用类型的数组(比如说a string[]
),则数组是string
对堆上各种对象的引用列表,因为引用是引用类型的值.在内部,这些引用实现为指向内存中地址的指针.如果你想看到这个,这样的数组在内存中会是这样的(在堆上):
[ 00000000, 00000000, 00000000, F8AB56AA ]
这是一个string
包含string
对堆上对象的4个引用的数组(这里的数字是十六进制的).目前,只有最后一个string
实际指向任何东西(内存在分配时初始化为所有零),这个数组基本上是C#中此代码的结果:
string[] strings = new string[4];
strings[3] = "something"; // the string was allocated at 0xF8AB56AA by the CLR
Run Code Online (Sandbox Code Playgroud)
上面的数组将是一个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
Run Code Online (Sandbox Code Playgroud)
该int[]
数组也将存储在堆上.
另一个例子,short[4]
数组的内存如下所示:
[ 0000, 0000, 0000, 0000 ]
作为值的short
是一个2字节数.
在存储值类型的情况下,这只是一个实现细节,正如Eric Lippert 在这里解释得非常好,并不是值和引用类型之间的差异所固有的(这是行为上的差异).
当你传递的东西的方法(是引用类型或值类型),则副本中的值类型实际上是传递给方法.在引用类型的情况下,该值是引用(将其视为指向一块内存的指针,尽管这也是一个实现细节),在值类型的情况下,该值本身就是它.
// 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){}
Run Code Online (Sandbox Code Playgroud)
仅当您将值类型转换为引用类型时才会出现限制.这个代码框:
object o = 5;
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
48177 次 |
最近记录: |