C# 数组、堆和堆栈以及值类型
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1113819/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Arrays, heap and stack and value types
提问by devoured elysium
int[] myIntegers;
myIntegers = new int[100];
In the above code, is new int[100] generating the array on the heap? From what I've read on CLR via c#, the answer is yes. But what I can't understand, is what happens to the actual int's inside the array. As they are value types, I'd guess they'd have to be boxed, as I can, for example, pass myIntegers to other parts of the program and it'd clutter up the stack if they were left on it all the time. Or am I wrong? I'd guess they'd just be boxed and would live on the heap for as long the array existed.
在上面的代码中,new int[100] 是在堆上生成数组吗?从我通过 c# 在 CLR 上阅读的内容来看,答案是肯定的。但我无法理解的是,数组内的实际 int 会发生什么。由于它们是值类型,我猜它们必须被装箱,例如,因为我可以将 myIntegers 传递给程序的其他部分,如果它们一直留在堆栈上,它会弄乱堆栈. 还是我错了?我猜他们只是被装箱,并且只要数组存在就会在堆上存在。
采纳答案by P Daddy
Your array is allocated on the heap, and the ints are not boxed.
您的数组在堆上分配,并且整数未装箱。
The source of your confusion is likely because people have said that reference types are allocated on the heap, and value types are allocated on the stack. This is not an entirely accurate representation.
您混淆的根源可能是因为人们说过引用类型是在堆上分配的,而值类型是在堆栈上分配的。这不是一个完全准确的表示。
All local variables and parameters are allocated on the stack. This includes both value types and reference types. The difference between the two is only what is storedin the variable. Unsurprisingly, for a value type, the valueof the type is stored directly in the variable, and for a reference type, the value of the type is stored on the heap, and a referenceto this value is what is stored in the variable.
所有局部变量和参数都分配在堆栈上。这包括值类型和引用类型。两者之间的区别仅在于变量中存储的内容。不出所料,对于值类型,类型的值直接存储在变量中,而对于引用类型,类型的值存储在堆中,对该值的引用就是存储在变量中的。
The same holds for fields. When memory is allocated for an instance of an aggregate type (a class
or a struct
), it must include storage for each of its instance fields. For reference-type fields, this storage holds just a reference to the value, which would itself be allocated on the heap later. For value-type fields, this storage holds the actual value.
字段也是如此。当为聚合类型(aclass
或 a struct
)的实例分配内存时,它必须包括其每个实例字段的存储。对于引用类型字段,此存储仅保存对该值的引用,该值本身稍后将在堆上分配。对于值类型字段,此存储保存实际值。
So, given the following types:
因此,鉴于以下类型:
class RefType{
public int I;
public string S;
public long L;
}
struct ValType{
public int I;
public string S;
public long L;
}
The values of each of these types would require 16 bytes of memory (assuming a 32-bit word size). The field I
in each case takes 4 bytes to store its value, the field S
takes 4 bytes to store its reference, and the field L
takes 8 bytes to store its value. So the memory for the value of both RefType
and ValType
looks like this:
这些类型中的每一种的值都需要 16 字节的内存(假设字长为 32 位)。字段I
在每种情况下需要4个字节来存储其值,则场S
需要4个字节来存储其引用,字段L
需要8个字节存储其值。所以两者的值的内存RefType
和ValType
看起来像这样:
0 ┌───────────────────┐ │ I │ 4 ├───────────────────┤ │ S │ 8 ├───────────────────┤ │ L │ │ │ 16 └───────────────────┘
Now if you had three local variables in a function, of types RefType
, ValType
, and int[]
, like this:
现在,如果你在一个函数中有三个局部变量,类型RefType
,ValType
和int[]
,就像这样:
RefType refType;
ValType valType;
int[] intArray;
then your stack might look like this:
那么您的堆栈可能如下所示:
0 ┌───────────────────┐ │ refType │ 4 ├───────────────────┤ │ valType │ │ │ │ │ │ │ 20 ├───────────────────┤ │ intArray │ 24 └───────────────────┘
If you assigned values to these local variables, like so:
如果您为这些局部变量赋值,如下所示:
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;
Then your stack might look something like this:
那么你的堆栈可能看起来像这样:
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 └───────────────────┘
Memory at address 0x4A963B68
(value of refType
) would be something like:
地址处的内存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 └───────────────────┘
Memory at address 0x4AA4C288
(value of intArray
) would be something like:
地址处的内存0x4AA4C288
(值intArray
)将类似于:
0 ┌───────────────────┐ │ 4 │ -- length of array 4 ├───────────────────┤ │ 300 │ -- `intArray[0]` 8 ├───────────────────┤ │ 301 │ -- `intArray[1]` 12 ├───────────────────┤ │ 302 │ -- `intArray[2]` 16 ├───────────────────┤ │ 303 │ -- `intArray[3]` 20 └───────────────────┘
Now, if you passed intArray
to another function, the value pushed onto the stack would be 0x4AA4C288
, the address of the array, nota copy of the array.
现在,如果您传递intArray
给另一个函数,压入堆栈的值将是0x4AA4C288
数组的地址,而不是数组的副本。
回答by JaredPar
Yes the array will be located on the heap.
是的,数组将位于堆上。
The ints inside the array will not be boxed. Just because a value type exists on the heap, does not necessarily mean it will be boxed. Boxing will only occur when a value type, such as int, is assigned to a reference of type object.
数组内的整数不会被装箱。仅仅因为值类型存在于堆上,并不一定意味着它会被装箱。仅当将值类型(例如 int)分配给类型 object 的引用时才会发生装箱。
For example
例如
Does not box:
不装箱:
int i = 42;
myIntegers[0] = 42;
Boxes:
盒子:
object i = 42;
object[] arr = new object[10]; // no boxing here
arr[0] = 42;
You may also want to check out Eric's post on this subject:
您可能还想查看 Eric 关于此主题的帖子:
回答by Dykam
An array of integers is allocated on the heap, nothing more, nothing less. myIntegers references to the start of the section where the ints are allocated. That reference is located on the stack.
一个整数数组被分配在堆上,不多也不少。myIntegers 引用分配整数的部分的开头。该引用位于堆栈上。
If you have a array of reference type objects, like the Object type, myObjects[], located on the stack, would reference to the bunch of values which reference the objects themselfes.
如果您有一个引用类型对象的数组,例如对象类型,位于堆栈上的 myObjects[] 将引用引用对象本身的一组值。
To summarize, if you pass myIntegers to some functions, you only pass the reference to the place where the real bunch of integers is allocated.
总而言之,如果您将 myIntegers 传递给某些函数,您只会将引用传递给分配真正整数的位置。
回答by Curt Nichols
There is no boxing in your example code.
您的示例代码中没有装箱。
Value types can live on the heap as they do in your array of ints. The array is allocated on the heap and it stores ints, which happen to be value types. The contents of the array are initialized to default(int), which happens to be zero.
值类型可以存在于堆中,就像它们存在于整数数组中一样。该数组在堆上分配并存储整数,这些整数恰好是值类型。数组的内容被初始化为 default(int),恰好为零。
Consider a class that contains a value type:
考虑一个包含值类型的类:
class HasAnInt
{
int i;
}
HasAnInt h = new HasAnInt();
Variable h refers to an instance of HasAnInt that lives on the heap. It just happens to contain a value type. That's perfectly okay, 'i' just happens to live on the heap as it's contained in a class. There is no boxing in this example either.
变量 h 指的是一个位于堆上的 HasAnInt 实例。它恰好包含一个值类型。这完全没问题,'i' 恰好位于堆上,因为它包含在一个类中。在这个例子中也没有拳击。
回答by Guffa
To understand what's happening, here are some facts:
要了解正在发生的事情,以下是一些事实:
- Object are always allocated on the heap.
- The heap only contains objects.
- Value types are either allocated on the stack, or part of an object on the heap.
- An array is an object.
- An array can only contain value types.
- An object reference is a value type.
- 对象总是在堆上分配。
- 堆只包含对象。
- 值类型要么在堆栈上分配,要么在堆上分配对象的一部分。
- 数组是一个对象。
- 数组只能包含值类型。
- 对象引用是一种值类型。
So, if you have an array of integers, the array is allocated on the heap and the integers that it contains is part of the array object on the heap. The integers reside inside the array object on the heap, not as separate objects, so they are not boxed.
因此,如果您有一个整数数组,则该数组在堆上分配,并且它包含的整数是堆上数组对象的一部分。整数驻留在堆上的数组对象内,而不是作为单独的对象,因此它们没有被装箱。
If you have an array of strings, it's really an array of string references. As references are value types they will be part of the array object on the heap. If you put a string object in the array, you actually put the reference to the string object in the array, and the string is a separate object on the heap.
如果你有一个字符串数组,它实际上是一个字符串引用数组。由于引用是值类型,它们将成为堆上数组对象的一部分。如果在数组中放入一个字符串对象,实际上就是将字符串对象的引用放入数组中,而字符串在堆上是一个单独的对象。
回答by JulianR
I think at the core of your question lies a misunderstanding about reference and value types. This is something probably every .NET and Java developer struggled with.
我认为您问题的核心在于对引用和值类型的误解。这可能是每个 .NET 和 Java 开发人员都在努力解决的问题。
An array is just a list of values. If it's an array of a reference type (say a string[]
) then the array is a list of references to various string
objects on the heap, as a reference is the valueof a reference type. Internally, these references are implemented as pointers to an address in memory. If you wish to visualize this, such an array would look like this in memory (on the heap):
数组只是一个值列表。如果它是一个引用类型的数组(比如 a string[]
),则该数组是string
对堆上各种对象的引用列表,因为引用是引用类型的值。在内部,这些引用被实现为指向内存中地址的指针。如果您希望将其可视化,这样的数组在内存中(在堆上)将如下所示:
[ 00000000, 00000000, 00000000, F8AB56AA ]
[ 00000000, 00000000, 00000000, F8AB56AA ]
This is an array of string
that contains 4 references to string
objects on the heap (the numbers here are hexadecimal). Currently, only the last string
actually points to anything (memory is initialized to all zero's when allocated), this array would basically be the result of this code in C#:
这是一个string
包含 4 个对string
堆上对象的引用的数组(这里的数字是十六进制的)。目前,只有最后一个string
实际指向任何东西(内存在分配时被初始化为全零),这个数组基本上是 C# 中这段代码的结果:
string[] strings = new string[4];
strings[3] = "something"; // the string was allocated at 0xF8AB56AA by the CLR
The above array would be in a 32 bit program. In a 64 bit program, the references would be twice as big (F8AB56AA
would be 00000000F8AB56AA
).
上面的数组将在 32 位程序中。在 64 位程序中,引用将是两倍大(F8AB56AA
将是00000000F8AB56AA
)。
If you have an array of value types (say an int[]
) then the array is a list of integers, as the valueof a value type isthe value itself (hence the name). The visualization of such an array would be this:
如果您有值类型的数组(说的int[]
),那么数组是整数列表,作为值的值类型是本身的价值(因此得名)。这样一个数组的可视化是这样的:
[ 00000000, 45FF32BB, 00000000, 00000000 ]
[ 00000000, 45FF32BB, 00000000, 00000000 ]
This is an array of 4 integers, where only the second int is assigned a value (to 1174352571, which is the decimal representation of that hexadecimal number) and the rest of the integers would be 0 (like I said, memory is initialized to zero and 00000000 in hexadecimal is 0 in decimal). The code that produced this array would be:
这是一个由 4 个整数组成的数组,其中只有第二个 int 被分配了一个值(到 1174352571,这是该十六进制数的十进制表示),其余的整数将为 0(就像我说的,内存被初始化为零而十六进制的 00000000 是十进制的 0)。产生这个数组的代码是:
int[] integers = new int[4];
integers[1] = 1174352571; // integers[1] = 0x45FF32BB would be valid too
This int[]
array would also be stored on the heap.
该int[]
数组也将存储在堆上。
As another example, the memory of a short[4]
array would look like this:
再举一个例子,short[4]
数组的内存看起来像这样:
[ 0000, 0000, 0000, 0000 ]
[ 0000, 0000, 0000, 0000 ]
As the valueof a short
is a 2 byte number.
作为值的short
是一个2字节数。
Where a value type is stored, is just an implementation detail as Eric Lippert explains very well here, not inherent to the differences between value and reference types (which is difference in behavior).
存储值类型的位置只是 Eric Lippert在这里解释得很好的一个实现细节,而不是值类型和引用类型之间的固有差异(这是行为的差异)。
When you pass something to a method (be that a reference type or a value type) then a copyof the valueof the type is actually passed to the method. In the case of a reference type, the valueis a reference (think of this as a pointer to a piece of memory, although that also is an implementation detail) and in the case of a value type, the value is the thing itself.
当您将某些内容传递给方法(无论是引用类型还是值类型)时,该类型的值的副本实际上会传递给该方法。在引用类型的情况下,值是一个引用(将其视为指向一块内存的指针,尽管这也是一个实现细节),而在值类型的情况下,值是事物本身。
// 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){}
Boxing only occurs if you converta value type to a reference type. This code boxes:
仅当将值类型转换为引用类型时才会发生装箱。此代码框:
object o = 5;
回答by gmaran23
Enough has been said by everybody, but if someone is looking for a clear (but non-official) sample and documentation about heap, stack, local variables, and static variables, refer the complete Jon Skeet's article on Memory in .NET - what goes where
每个人都说得够多了,但如果有人正在寻找关于堆、堆栈、局部变量和静态变量的清晰(但非官方)示例和文档,请参阅 Jon Skeet 完整的关于.NET 中的内存的文章——结果如何在哪里
Excerpt:
摘抄:
Each local variable (ie one declared in a method) is stored on the stack. That includes reference type variables - the variable itself is on the stack, but remember that the value of a reference type variable is only a reference (or null), not the object itself. Method parameters count as local variables too, but if they are declared with the ref modifier, they don't get their own slot, but share a slot with the variable used in the calling code. See my article on parameter passing for more details.
Instance variables for a reference type are always on the heap. That's where the object itself "lives".
Instance variables for a value type are stored in the same context as the variable that declares the value type. The memory slot for the instance effectively contains the slots for each field within the instance. That means (given the previous two points) that a struct variable declared within a method will always be on the stack, whereas a struct variable which is an instance field of a class will be on the heap.
Every static variable is stored on the heap, regardless of whether it's declared within a reference type or a value type. There is only one slot in total no matter how many instances are created. (There don't need to be any instances created for that one slot to exist though.) The details of exactly which heap the variables live on are complicated, but explained in detail in an MSDN article on the subject.
每个局部变量(即在方法中声明的变量)都存储在堆栈中。这包括引用类型变量 - 变量本身在堆栈中,但请记住,引用类型变量的值只是一个引用(或 null),而不是对象本身。方法参数也算作局部变量,但如果它们是用 ref 修饰符声明的,它们不会获得自己的槽,而是与调用代码中使用的变量共享一个槽。有关更多详细信息,请参阅我关于参数传递的文章。
引用类型的实例变量总是在堆上。那就是对象本身“生活”的地方。
值类型的实例变量与声明值类型的变量存储在同一上下文中。实例的内存槽有效地包含了实例内每个字段的槽。这意味着(鉴于前两点)在方法中声明的结构变量将始终在堆栈上,而作为类的实例字段的结构变量将在堆上。
每个静态变量都存储在堆中,无论它是在引用类型还是值类型中声明的。无论创建多少个实例,总共只有一个槽位。(虽然不需要为该插槽创建任何实例才能存在。)变量存在于哪个堆上的详细信息很复杂,但在有关该主题的 MSDN 文章中进行了详细解释。