我对堆栈与堆之间的内存分配基础感到困惑。根据标准定义(每个人都说的话),所有值类型将被分配到堆栈和参考类型将进入堆即可。
现在考虑以下示例:
class MyClass
{
int myInt = 0;
string myString = "Something";
}
class Program
{
static void Main(string[] args)
{
MyClass m = new MyClass();
}
}
现在,如何在c#中进行内存分配? MyClass
的对象(即m
)是否会完全分配给堆?也就是说,int myInt
和string myString
都会堆积?
或者,该对象将分为两部分,并将分配给堆栈和堆的两个内存位置?
答案 0 :(得分:58)
您应该考虑将对象分配为实现细节的问题。对您来说,确切地存储对象位的位置并不重要。对象是引用类型还是值类型可能很重要,但在开始必须优化垃圾收集行为之前,您不必担心它将存储在何处。
虽然在当前实现中始终在堆上分配引用类型,但是值类型可能可以在堆栈上分配 - 但不一定如此。值类型仅在堆栈上分配时,它是未包装的非转义本地或临时变量,未包含在引用类型中且未在寄存器中分配。
我错过了什么吗?
当然,如果我没有链接到Eric Lippert关于这个主题的帖子,我将会失职:
答案 1 :(得分:52)
m
在堆上分配,包括myInt
。在堆栈上分配基本类型(和结构)的情况是在方法调用期间,它为堆栈上的局部变量分配空间(因为它更快)。例如:
class MyClass
{
int myInt = 0;
string myString = "Something";
void Foo(int x, int y) {
int rv = x + y + myInt;
myInt = 2^rv;
}
}
rv
,x
,y
都将在堆叠中。 myInt
位于堆上的某个位置(必须通过this
指针访问)。
答案 2 :(得分:20)
“所有VALUE类型将被分配到堆栈”是非常非常错误的;结构变量可以作为方法变量存在于堆栈中。但是,类型上的字段与该类型一起使用。如果字段的声明类型是类,则值在堆上作为该对象的部分。如果字段的声明类型是结构,则字段是该结构的一部分该结构所在的。
即使方法变量可以在堆上,如果它们是捕获(lambda / anon-method),或者是(例如)迭代器块的一部分。< / p>
答案 3 :(得分:10)
优秀的解释:
答案 4 :(得分:1)
简单的措施
值类型可以在THE STACK上进行,它是可以分配给某些未来主义者数据结构的实现细节。
所以,最好理解值和引用类型是如何工作的,值类型将被值复制,这意味着当您将值类型作为参数传递给FUNCTION时,它将被自然复制意味着您将拥有全新副本。
引用类型通过引用传递(againg不考虑引用将在某些未来版本中再次存储地址,它可能存储在其他一些数据结构中。)
所以在你的情况下
myInt是一个int,它在一个类中被封装在一个类中,它就是一个引用类型,因此它将被绑定到将存储在'THE HEAP'上的类的实例。
我建议,你可以开始阅读ERIC LIPPERTS写的博客。
答案 5 :(得分:1)
每次在其中创建对象时,都会进入称为堆的内存区域。原始变量就像 如果它们是本地方法变量,则在堆栈中分配int和double,如果它们是成员,则在堆中分配 变量。在方法中,当调用方法时,局部变量被推入堆栈 当方法调用完成时,堆栈指针递减。在每个线程的多线程应用程序中 将拥有自己的堆栈,但将共享相同的堆。这就是为什么要在代码中注意避免任何问题 堆空间中的并发访问问题。堆栈是线程安全的(每个线程都有自己的堆栈)但是 除非通过代码保护同步,否则堆不是线程安全的。
此链接也很有用http://www.programmerinterview.com/index.php/data-structures/difference-between-stack-and-heap/
答案 6 :(得分:1)
stack
是用于存储local variables
和parameters
的内存块。当输入和退出函数时,堆栈在逻辑上增长和缩小。
考虑以下方法:
public static int Factorial (int x)
{
if (x == 0)
{
return 1;
}
return x * Factorial (x - 1);
}
此方法是递归的,这意味着它会调用自身。 每次输入方法时,都会在堆栈上分配新的int ,每次方法退出时,int都会被释放。
- 堆是
objects
(即reference-type instances
)所在的内存块。每当创建一个新对象时,它就会在堆上分配,并返回对该对象的引用。在程序执行期间,堆会在创建新对象时开始填充。运行时有一个垃圾收集器,它定期从堆中释放对象,因此您的程序不会运行Out Of Memory
。一旦某个对象本身alive
没有被引用,就有资格取消分配。- 堆还存储
static fields
。与堆上分配的对象(可以进行垃圾收集)不同,these live until the application domain is torn down
。
考虑以下方法:
using System;
using System.Text;
class Test
{
public static void Main()
{
StringBuilder ref1 = new StringBuilder ("object1");
Console.WriteLine (ref1);
// The StringBuilder referenced by ref1 is now eligible for GC.
StringBuilder ref2 = new StringBuilder ("object2");
StringBuilder ref3 = ref2;
// The StringBuilder referenced by ref2 is NOT yet eligible for GC.
Console.WriteLine (ref3); // object2
}
}
在上面的例子中,我们首先创建一个由变量ref1引用的StringBuilder对象,然后写出它的内容。然后,StringBuilder对象立即有资格进行垃圾回收,因为之后没有任何内容使用它。然后,我们创建另一个由变量ref2引用的StringBuilder,并将该引用复制到ref3。即使在该点之后没有使用ref2,ref3也会使相同的StringBuilder对象保持活动状态 - 确保在完成使用ref3之前它不符合收集条件。
值变量实例(和对象引用)存在于变量所在的任何位置 声明。如果实例被声明为类类型中的字段或数组元素,那么该实例将存在于堆上。
答案 7 :(得分:0)
m是对MyClass对象的引用,因此m存储在主线程的堆栈中,但MyClass的对象存储在堆中。因此myInt和myString存储在堆中。 请注意,m只是一个引用(内存的地址)并且位于主堆栈上。当m deallocated然后GC清除堆中的MyClass对象 有关更多详细信息,请阅读本文的所有四个部分 https://www.c-sharpcorner.com/article/C-Sharp-heaping-vs-stacking-in-net-part-i/
答案 8 :(得分:-1)
根据标准定义(每个人都说过的事情),所有值类型都将分配到堆栈中,而引用类型将进入堆中。
这是错误的。只有本地(在函数上下文中)值类型/值类型数组在堆栈上分配。其他所有内容都在堆上分配。