在结构上使用“新”是否将它分配到堆或栈上?

当用new运算符创build一个类的实例时,内存将被分配到堆上。 当你用new运算符创build一个结构体的实例时,内存被分配到哪里,在堆上还是在栈上?

好的,让我们看看我能否做得更清楚。

首先,Ash是正确的:问题在于值typesvariables的分配。 这是一个不同的问题 – 答案不仅仅是“堆叠”。 这比这更复杂(而且由C#2变得更加复杂)。 我有一个关于这个话题的文章,如果需要的话,将会扩展它,但是让我们来处理这个new操作符。

其次,这一切都取决于你在说什么水平。 我正在研究编译器在源代码上做了什么,就它创build的IL而言。 JIT编译器在优化掉相当多的“逻辑”分配方面会做很多巧妙的事情。

第三,我忽略了generics,主要是因为我实际上并不知道答案,一方面是因为这太复杂了。

最后,所有这些只是目前的实施。 C#规范没有详细说明 – 它实际上是一个实现细节。 有些人认为托pipe代码开发人员真的不应该在意。 我不确定我会走多远,但是值得一想的是,这个世界其实所有的局部variables都在堆上 – 这仍然符合规范。


new运算符在值types上有两种不同的情况:您可以调用无参数构造函数(例如new Guid() )或有参数的构造函数(例如new Guid(someString) )。 这些产生显着不同的IL。 要理解为什么,您需要比较C#和CLI规范:根据C#,所有值types都有一个无参数的构造函数。 根据CLI规范, 没有值types具有无参数的构造函数。 (使用reflection获取值types的构造函数 – 您将不会find无参数的值)

C#将“用零初始化一个值”作为构造函数是有道理的,因为它保持了语言的一致性 – 您可以将new(...)视为始终调用构造函数。 CLI有不同的想法是有道理的,因为没有真正的代码可以调用 – 当然也没有types特定的代码。

在初始化之后,您将如何处理值。 IL用于

 Guid localVariable = new Guid(someString); 

与IL所使用的不同:

 myInstanceOrStaticVariable = new Guid(someString); 

另外,如果该值被用作中间值,例如方法调用的参数,那么事情又会稍有不同。 为了显示所有这些差异,这里有一个简短的testing程序。 它没有显示静态variables和实例variables之间的差异:IL将在stfldstsfld之间有所不同,但是全部。

 using System; public class Test { static Guid field; static void Main() {} static void MethodTakingGuid(Guid guid) {} static void ParameterisedCtorAssignToField() { field = new Guid(""); } static void ParameterisedCtorAssignToLocal() { Guid local = new Guid(""); // Force the value to be used local.ToString(); } static void ParameterisedCtorCallMethod() { MethodTakingGuid(new Guid("")); } static void ParameterlessCtorAssignToField() { field = new Guid(); } static void ParameterlessCtorAssignToLocal() { Guid local = new Guid(); // Force the value to be used local.ToString(); } static void ParameterlessCtorCallMethod() { MethodTakingGuid(new Guid()); } } 

下面是这个类的IL,排除不相关的比特(比如nops):

 .class public auto ansi beforefieldinit Test extends [mscorlib]System.Object { // Removed Test's constructor, Main, and MethodTakingGuid. .method private hidebysig static void ParameterisedCtorAssignToField() cil managed { .maxstack 8 L_0001: ldstr "" L_0006: newobj instance void [mscorlib]System.Guid::.ctor(string) L_000b: stsfld valuetype [mscorlib]System.Guid Test::field L_0010: ret } .method private hidebysig static void ParameterisedCtorAssignToLocal() cil managed { .maxstack 2 .locals init ([0] valuetype [mscorlib]System.Guid guid) L_0001: ldloca.s guid L_0003: ldstr "" L_0008: call instance void [mscorlib]System.Guid::.ctor(string) // Removed ToString() call L_001c: ret } .method private hidebysig static void ParameterisedCtorCallMethod() cil managed { .maxstack 8 L_0001: ldstr "" L_0006: newobj instance void [mscorlib]System.Guid::.ctor(string) L_000b: call void Test::MethodTakingGuid(valuetype [mscorlib]System.Guid) L_0011: ret } .method private hidebysig static void ParameterlessCtorAssignToField() cil managed { .maxstack 8 L_0001: ldsflda valuetype [mscorlib]System.Guid Test::field L_0006: initobj [mscorlib]System.Guid L_000c: ret } .method private hidebysig static void ParameterlessCtorAssignToLocal() cil managed { .maxstack 1 .locals init ([0] valuetype [mscorlib]System.Guid guid) L_0001: ldloca.s guid L_0003: initobj [mscorlib]System.Guid // Removed ToString() call L_0017: ret } .method private hidebysig static void ParameterlessCtorCallMethod() cil managed { .maxstack 1 .locals init ([0] valuetype [mscorlib]System.Guid guid) L_0001: ldloca.s guid L_0003: initobj [mscorlib]System.Guid L_0009: ldloc.0 L_000a: call void Test::MethodTakingGuid(valuetype [mscorlib]System.Guid) L_0010: ret } .field private static valuetype [mscorlib]System.Guid field } 

正如你所看到的,有很多不同的指令用于调用构造函数:

  • newobj :分配栈中的值,调用一个参数化的构造函数。 用于中间值,例如分配给字段或用作方法参数。
  • call instance :使用已经分配的存储位置(不pipe是否在堆栈上)。 这在上面的代码中用于分配给局部variables。 如果使用几次new调用将相同的局部variables多次分配一个值,则它只是初始化旧值的顶部的数据 – 它不会每次分配更多的堆栈空间。
  • initobj :使用已经分配的存储位置,只擦除数据。 这用于我们所有的无参数构造函数调用,包括那些分配给局部variables的调用。 对于方法调用,有效地引入了一个中间局部variables,其值由initobj

我希望这个话题能够说明这个话题是多么复杂,同时又是一个亮点。 在一些概念的意义上,每一次new调用都在堆栈中分配空间 – 但正如我们所看到的,即使在IL级别也不是真正发生的事情。 我想强调一个特例。 采取这种方法:

 void HowManyStackAllocations() { Guid guid = new Guid(); // [...] Use guid guid = new Guid(someBytes); // [...] Use guid guid = new Guid(someString); // [...] Use guid } 

这个“逻辑上”有4个堆栈分配 – 一个用于variables,一个用于三个new调用 – 但事实上(对于特定的代码)堆栈只分配一次,然后重用相同的存储位置。

编辑:只是要清楚,这只是在某些情况下是正确的…尤其是, guid的值将不可见如果Guid构造函数抛出一个exception,这就是为什么C#编译器能够重用相同的堆栈插槽。 有关更多详细信息和适用的情况,请参阅Eric Lippert 关于价值types构build的博客文章 。

我在写这个答案的过程中学到了很多,如果有任何不清楚的地方,请澄清一下。

包含struct字段的内存可以根据具体情况分配在堆栈或堆上。 如果struct-typevariables是某个匿名委托或迭代器类未捕获的局部variables或参数,则它将被分配到堆栈上。 如果variables是某个类的一部分,那么它将被分配到堆中的类中。

如果struct被分配在堆上,那么调用new运算符实际上不是分配内存所必需的。 唯一的目的是根据构造函数中的内容设置字段值。 如果构造函数没有被调用,那么所有的字段将得到它们的默认值(0或null)。

类似地,对于堆栈中分配的结构,除了C#要求所有局部variables在使用之前被设置为某个值之外,所以您必须调用自定义构造函数或默认构造函数(不带参数的构造函数始终可用于结构)。

简而言之,新结构是结构错误的一种,称新结构只是简单地称为构造函数。 结构的唯一存储位置是它所定义的位置。

如果它是一个成员variables,它将直接存储在它定义的任何variables中,如果它是一个局部variables或参数,它将被存储在堆栈中。

将其与类进行对比,类中有结构可能已被完整存储的地方,而引用指向堆的某处。 (成员,本地/堆栈参数)

这可能有助于看起来有点到C + +,其中没有真正的类/结构之间的区别。 (在语言中有类似的名字,但它们只是指默认的可访问性)当你调用new时,你得到一个指向堆的位置的指针,而如果你有一个非指针的引用,它将直接存储在堆栈上,或者在另一个对象中,C#中的ala结构。

与所有值types一样,结构总是在声明的位置

有关何时使用结构的更多详细信息,请参阅此处的问题。 这个问题在这里的结构更多的信息。

编辑:我错误地回答说,他们总是在堆栈中。 这是不正确的 。

我可能在这里错过了一些东西,但为什么我们关心分配?

值types是通过值传递的;)因此不能在与定义的范围不同的范围进行变异。 为了能够改变值,你必须添加[ref]关键字。

引用types通过引用传递,可以进行变异。

当然,不可变的引用typesstring是最stream行的。

数组布局/初始化:值types – >零内存[name,zip] [name,zip]引用types – >零内存 – > null [ref] [ref]

classstruct声明就像是用来在运行时创build实例或对象的蓝图。 如果您定义了一个名为Person的classstruct ,那么Person就是该types的名称。 如果声明并初始化Persontypes的variablesp,则称p为Person的对象或实例。 可以创build同一个Persontypes的多个实例,并且每个实例在其propertiesfields可以具有不同的值。

一个class是一个引用types。 当class一个对象被创build时,对象被赋值的variables只保存对该内存的引用。 当对象引用被分配给新variables时,新variables引用原始对象。 通过一个variables所做的更改反映在另一个variables中,因为它们都指向相同的数据。

struct是一个值types。 当一个struct被创build时, struct被分配的variables保存结构的实际数据。 当struct被分配给一个新的variables时,它被复制。 新variables和原始variables因此包含相同数据的两个单独的副本。 对一个副本所做的更改不会影响另一个副本。

通常, classes用于build模更复杂的行为,或者在创buildclass对象后打算修改的数据。 Structs最适合于小数据结构,主要包含在创buildstruct之后不打算修改的数据。

更多…

几乎所有被视为值types的结构都分配在堆栈上,而对象则分配在堆上,而对象引用(指针)则分配到堆栈上。

结构被分配到堆栈。 这是一个有用的解释:

结构

另外,在.NET中实例化的类在堆或.NET的保留内存空间上分配内存。 而由于堆栈上的分配,结构在实例化时会产生更高的效率。 此外,应该注意的是,在结构中传递参数是通过值来完成的。