C# 在结构上使用“new”是在堆还是堆栈上分配它?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/203695/
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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 17:53:43  来源:igfitidea点击:

Does using "new" on a struct allocate it on the heap or stack?

c#.netmemory-management

提问by kedar kamthe

When you create an instance of a class with the newoperator, memory gets allocated on the heap. When you create an instance of a struct with the newoperator where does the memory get allocated, on the heap or on the stack ?

当您使用new运算符创建类的实例时,内存会在堆上分配。当您使用new运算符创建结构的实例时,内存在哪里分配,在堆上还是在堆栈上?

采纳答案by Jon Skeet

Okay, let's see if I can make this any clearer.

好吧,让我们看看我能不能说得更清楚一点。

Firstly, Ash is right: the question is notabout where value type variablesare allocated. That's a different question - and one to which the answer isn't just "on the stack". It's more complicated than that (and made even more complicated by C# 2). I have an article on the topicand will expand on it if requested, but let's deal with just the newoperator.

首先,Ash 是对的:问题在于值类型变量的分配位置。这是一个不同的问题 - 答案不仅仅是“在堆栈上”。它比那更复杂(并且被 C# 2 变得更加复杂)。我有一篇关于该主题文章,如果需要,我将对其进行扩展,但让我们只处理new操作员。

Secondly, all of this really depends on what level you're talking about. I'm looking at what the compiler does with the source code, in terms of the IL it creates. It's more than possible that the JIT compiler will do clever things in terms of optimising away quite a lot of "logical" allocation.

其次,所有这一切实际上取决于您所谈论的级别。我正在查看编译器如何处理源代码,就它创建的 IL 而言。JIT 编译器很可能会在优化大量“逻辑”分配方面做一些聪明的事情。

Thirdly, I'm ignoring generics, mostly because I don't actually know the answer, and partly because it would complicate things too much.

第三,我忽略了泛型,主要是因为我实际上并不知道答案,部分原因是它会使事情变得过于复杂。

Finally, all of this is just with the current implementation. The C# spec doesn't specify much of this - it's effectively an implementation detail. There are those who believe that managed code developers really shouldn't care. I'm not sure I'd go that far, but it's worth imagining a world where in fact all local variables live on the heap - which would still conform with the spec.

最后,所有这些都仅适用于当前的实现。C# 规范并没有详细说明这一点 - 它实际上是一个实现细节。有些人认为托管代码开发人员真的不应该关心。我不确定我会走那么远,但值得想象一个实际上所有局部变量都存在于堆上的世界 - 这仍然符合规范。



There are two different situations with the newoperator on value types: you can either call a parameterless constructor (e.g. new Guid()) or a parameterful constructor (e.g. new Guid(someString)). These generate significantly different IL. To understand why, you need to compare the C# and CLI specs: according to C#, all value types have a parameterless constructor. According to the CLI spec, novalue types have parameterless constructors. (Fetch the constructors of a value type with reflection some time - you won't find a parameterless one.)

new值类型上的运算符有两种不同的情况:您可以调用无参数构造函数(例如new Guid())或有参数构造函数(例如new Guid(someString))。这些产生显着不同的IL。要了解原因,您需要比较 C# 和 CLI 规范:根据 C#,所有值类型都有一个无参数构造函数。根据 CLI 规范,没有值类型具有无参数构造函数。(有时使用反射获取值类型的构造函数-您不会找到无参数的构造函数。)

It makes sense for C# to treat the "initialize a value with zeroes" as a constructor, because it keeps the language consistent - you can think of new(...)as alwayscalling a constructor. It makes sense for the CLI to think of it differently, as there's no real code to call - and certainly no type-specific code.

C# 将“用零初始化值”视为构造函数是有意义的,因为它使语言保持一致 - 您可以将其new(...)视为始终调用构造函数。CLI 以不同的方式思考它是有意义的,因为没有真正的代码可以调用——当然也没有特定于类型的代码。

It also makes a difference what you're going to do with the value after you've initialized it. The IL used for

初始化值后,您将如何处理该值也会有所不同。IL 用于

Guid localVariable = new Guid(someString);

is different to the IL used for:

与用于以下用途的 IL 不同:

myInstanceOrStaticVariable = new Guid(someString);

In addition, if the value is used as an intermediate value, e.g. an argument to a method call, things are slightly different again. To show all these differences, here's a short test program. It doesn't show the difference between static variables and instance variables: the IL would differ between stfldand stsfld, but that's all.

此外,如果该值用作中间值,例如方法调用的参数,则情况又略有不同。为了显示所有这些差异,这里有一个简短的测试程序。它没有显示静态变量和实例变量之间的区别:IL 在stfld和之间会有所不同stsfld,但仅此而已。

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());
    }
}

Here's the IL for the class, excluding irrelevant bits (such as nops):

这是该类的 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
}

As you can see, there are lots of different instructions used for calling the constructor:

如您所见,有许多不同的指令用于调用构造函数:

  • newobj: Allocates the value on the stack, calls a parameterised constructor. Used for intermediate values, e.g. for assignment to a field or use as a method argument.
  • call instance: Uses an already-allocated storage location (whether on the stack or not). This is used in the code above for assigning to a local variable. If the same local variable is assigned a value several times using several newcalls, it just initializes the data over the top of the old value - it doesn'tallocate more stack space each time.
  • initobj: Uses an already-allocated storage location and just wipes the data. This is used for all our parameterless constructor calls, including those which assign to a local variable. For the method call, an intermediate local variable is effectively introduced, and its value wiped by initobj.
  • newobj:分配堆栈上的值,调用参数化构造函数。用于中间值,例如分配给字段或用作方法参数。
  • call instance:使用已经分配的存储位置(无论是否在堆栈上)。这在上面的代码中用于分配给局部变量。如果使用多次new调用多次为同一个局部变量赋值,它只会在旧值的顶部初始化数据 - 它不会每次分配更多的堆栈空间。
  • initobj:使用已分配的存储位置并仅擦除数据。这用于我们所有的无参数构造函数调用,包括那些分配给局部变量的调用。对于方法调用,有效地引入了一个中间局部变量,其值被initobj.

I hope this shows how complicated the topic is, while shining a bit of light on it at the same time. In someconceptual senses, every call to newallocates space on the stack - but as we've seen, that isn't what really happens even at the IL level. I'd like to highlight one particular case. Take this method:

我希望这表明这个话题有多么复杂,同时也能说明一点。在某些概念上,每次调用都会new在堆栈上分配空间——但正如我们所见,即使在 IL 级别,这也不是真正发生的事情。我想强调一个特殊的案例。采取这个方法:

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

That "logically" has 4 stack allocations - one for the variable, and one for each of the three newcalls - but in fact (for that specific code) the stack is only allocated once, and then the same storage location is reused.

“逻辑上”有 4 个堆栈分配 - 一个用于变量,一个用于三个new调用中的每一个- 但实际上(对于该特定代码)堆栈仅分配一次,然后重复使用相同的存储位置。

EDIT: Just to be clear, this is only true in some cases... in particular, the value of guidwon't be visible if the Guidconstructor throws an exception, which is why the C# compiler is able to reuse the same stack slot. See Eric Lippert's blog post on value type constructionfor more details and a case where it doesn'tapply.

编辑:明确地说,这仅在某些情况下是正确的......特别是,guid如果Guid构造函数抛出异常,则 的值将不可见,这就是 C# 编译器能够重用相同堆栈槽的原因。有关更多详细信息和适用的情况,请参阅 Eric Lippert 的关于值类型构造博客文章

I've learned a lot in writing this answer - please ask for clarification if any of it is unclear!

我在写这个答案时学到了很多东西 - 如果有任何不清楚的地方,请要求澄清!

回答by Esteban Araya

As with all value types, structs always go where they were declared.

与所有值类型一样,结构总是在声明它们的地方。

See this question herefor more details on when to use structs. And this question herefor some more info on structs.

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

Edit:I had mistankely answered that they ALWAYSgo in the stack. This is incorrect.

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

回答by DaveK

Structs get allocated to the stack. Here is a helpful explanation:

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

Structs

结构

Additionally, classes when instantiated within .NET allocate memory on the heap or .NET's reserved memory space. Whereas structs yield more efficiency when instantiated due to allocation on the stack. Furthermore, it should be noted that passing parameters within structs are done so by value.

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

回答by Jeffrey L Whitledge

The memory containing a struct's fields can be allocated on either the stack or the heap depending on the circumstances. If the struct-type variable is a local variable or parameter that is not captured by some anonymous delegate or iterator class, then it will be allocated on the stack. If the variable is part of some class, then it will be allocated within the class on the heap.

包含结构域的内存可以根据情况分配在堆栈或堆上。如果结构类型变量是一个局部变量或参数,没有被某个匿名委托或迭代器类捕获,那么它将被分配在堆栈上。如果变量是某个类的一部分,那么它将在堆上的类中分配。

If the struct is allocated on the heap, then calling the new operator is not actually necessary to allocate the memory. The only purpose would be to set the field values according to whatever is in the constructor. If the constructor is not called, then all the fields will get their default values (0 or null).

如果结构是在堆上分配的,那么实际上不需要调用 new 运算符来分配内存。唯一的目的是根据构造函数中的内容设置字段值。如果未调用构造函数,则所有字段都将获得其默认值(0 或 null)。

Similarly for structs allocated on the stack, except that C# requires all local variables to be set to some value before they are used, so you have to call either a custom constructor or the default constructor (a constructor that takes no parameters is always available for structs).

对于在堆栈上分配的结构体也是如此,除了 C# 要求所有局部变量在使用之前都设置为某个值,因此您必须调用自定义构造函数或默认构造函数(不带参数的构造函数始终可用于结构)。

回答by bashmohandes

Pretty much the structs which are considered Value types, are allocated on stack, while objects get allocated on heap, while the object reference (pointer) gets allocated on the stack.

几乎被视为值类型的结构在堆栈上分配,而对象在堆上分配,而对象引用(指针)在堆栈上分配。

回答by Guvante

To put it compactly, new is a misnomer for structs, calling new simply calls the constructor. The only storage location for the struct is the location it is defined.

简而言之,new 是结构的用词不当,调用 new 只是调用构造函数。结构体的唯一存储位置是它定义的位置。

If it is a member variable it is stored directly in whatever it is defined in, if it is a local variable or parameter it is stored on the stack.

如果是成员变量,则直接存储在定义的任何内容中,如果是局部变量或参数,则存储在堆栈中。

Contrast this to classes, which have a reference wherever the struct would have been stored in its entirety, while the reference points somewhere on the heap. (Member within, local/parameter on stack)

将此与类进行对比,类在结构体完整存储的任何地方都有引用,而引用指向堆上的某处。(内部成员,堆栈上的本地/参数)

It may help to look a bit into C++, where there is not real distinction between class/struct. (There are similar names in the language, but they only refer to the default accessibility of things) When you call new you get a pointer to the heap location, while if you have a non-pointer reference it is stored directly on the stack or within the other object, ala structs in C#.

稍微研究一下 C++ 可能会有所帮助,其中类/结构之间没有真正的区别。(语言中有类似的名称,但它们仅指事物的默认可访问性)当您调用 new 时,您将获得一个指向堆位置的指针,而如果您有一个非指针引用,则它直接存储在堆栈中或在另一个对象中,ala 结构是 C# 中的。

回答by user18579

I'm probably missing something here but why do we care about allocation?

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

Value types are passed by value ;) and thus can't be mutated at a different scope than where they are defined. To be able to mutate the value you have to add the [ref] keyword.

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

Reference types are passed by reference and can be mutated.

引用类型是通过引用传递的,并且可以改变。

There are of course immutable reference types strings being the most popular one.

当然,不可变引用类型字符串是最流行的一种。

Array layout/initialization: Value types -> zero memory [name,zip][name,zip] Reference types -> zero memory -> null [ref][ref]

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

回答by Sujit

A classor structdeclaration is like a blueprint that is used to create instances or objects at run time. If you define a classor structcalled Person, Person is the name of the type. If you declare and initialize a variable p of type Person, p is said to be an object or instance of Person. Multiple instances of the same Person type can be created, and each instance can have different values in its propertiesand fields.

一个classstruct声明就像是被用来在运行时创建实例或对象的蓝图。如果您定义了一个classstruct称为 Person 的,则 Person 是该类型的名称。如果声明并初始化一个 Person 类型的变量 p,则称 p 是 Person 的对象或实例。同一个人类型的多个实例可以被创建,并且每个实例都可以在其具有不同的价值观propertiesfields

A classis a reference type. When an object of the classis created, the variable to which the object is assigned holds only a reference to that memory. When the object reference is assigned to a new variable, the new variable refers to the original object. Changes made through one variable are reflected in the other variable because they both refer to the same data.

Aclass是引用类型。当的一个对象class被创建,以将对象分配变量仅保持到存储器中的参考。当对象引用分配给新变量时,新变量引用原始对象。通过一个变量所做的更改会反映在另一个变量中,因为它们都引用相同的数据。

A structis a value type. When a structis created, the variable to which the structis assigned holds the struct's actual data. When the structis assigned to a new variable, it is copied. The new variable and the original variable therefore contain two separate copies of the same data. Changes made to one copy do not affect the other copy.

Astruct是值类型。当struct被创建,以该变量struct被分配保持结构体的实际数据。当struct分配给一个新变量时,它被复制。因此,新变量和原始变量包含相同数据的两个单独副本。对一个副本所做的更改不会影响另一个副本。

In general, classesare used to model more complex behavior, or data that is intended to be modified after a classobject is created. Structsare best suited for small data structures that contain primarily data that is not intended to be modified after the structis created.

通常,classes用于对更复杂的行为或在class创建对象后要修改的数据进行建模。Structs最适用于主要包含在struct创建后不打算修改的数据的小型数据结构。

for more...

更多...