是否使用"新"在一个结构它分配在堆或栈? [英] Does using "new" on a struct allocate it on the heap or stack?

查看:195
本文介绍了是否使用"新"在一个结构它分配在堆或栈?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当你创建一个类的实例与运营商,内存被分配在堆上。当你创建一个运营商结构哪里内存得到分配,在堆或堆栈?

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

推荐答案

注:C#6,您可以编写自定义参数的构造函数的结构。这个答案写在这之前,我还没有订正,以适应这种变化。 (事情变得复杂的时候调用构造函数而言,在阵列中,仿制药等方面。)

Note: C# 6 allows you to write a custom parameterless constructor for structs. This answer was written before that, and I haven't revised it to accommodate that change. (Things get complicated in terms of when the constructor is called, in terms of arrays, generics etc.)

好了,让我们来看看,如果我可以让这个更清楚。

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

首先,灰是正确的:问题的没有的哪里值类型的变量的分配。这是一个不同的问题 - 和一个问题的答案是不只是在堆栈上。它比这更复杂(甚至更加复杂的C#2发)。我的话题的文章,如果要求它的服务将扩大,但让我们处理在运营商。

Firstly, Ash is right: the question is not about where value type variables are 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 topic and will expand on it if requested, but let's deal with just the new operator.

其次,这一切真的取决于什么水平,你在说什么。我望着眼前的编译器与源$ C ​​$ C,在它创建的​​IL方面。它比有可能是JIT编译器会做聪明的事情在优化掉相当多的逻辑分配方面。

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.

第三,我忽视仿制药,主要是因为我真的不知道答案,部分是因为它将复杂的事情太多了。

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

最后,这一切仅仅是与当前实现。 C#的规范没有明确规定这在很大程度上 - 这是一个有效的实现细节。还有那些谁相信管理code开发商真的不应该关心。我不知道我会走那么远,但它是值得想象的世界里,其实都是局部变量在堆中生活 - 这仍与规格符合

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.

有两种不同的情况与运营商对价值类型:你可以调用一个无参数的构造函数(如新的GUID())或parameterful构造(如新的GUID(someString))。这些产生显著不同的IL。要理解为什么,你需要比较C#和CLI规格:按C#中,所有的值类型有一个参数的构造函数。按照CLI规范,没有的值类型有参数构造函数。 (取一个值类型的构造与反思一段时间 - 你不会找到一个无参数的)

There are two different situations with the new operator 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, no value types have parameterless constructors. (Fetch the constructors of a value type with reflection some time - you won't find a parameterless one.)

这是有道理的C#来看待初始化零值作为构造,因为它使语言保持一致 - 你能想到的新(...)总是的调用构造函数。这是有道理的CLI来认为它不同的,因为没有真正的code调用 - 当然也没有特定类型的code

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 always calling 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.

这也影响到你打算做什么用的价值,你已经初始化之后。该IL用于

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

Guid localVariable = new Guid(someString);

是不同与IL用于:

is different to the IL used for:

myInstanceOrStaticVariable = new Guid(someString);

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

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 stfld and stsfld, but that's all.

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为类,不包括无关位(如NOP指令):

Here's the IL for the class, excluding irrelevant bits (such as 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 :分配堆栈上的值,调用参数化构造函数。用于中间值,例如用于分配给一个字段或作为方法的参数使用。
  • 呼叫实例:使用一个已经分配的存储位置(无论是在堆栈或没有)。这是用在code以上用于分配到的本地变量。如果同一个局部变量使用几个呼叫分配一个值几次,它只是初始化过的旧值顶部的数据 - 它的的每次分配更多的堆栈空间。
  • initobj :使用一个已经分配的存储位置,只是抹数据。这是用于所有参数的构造函数调用,包括那些分配到一个局部变量。对于方法调用,中间局部变量有效地推出,其价值被抹去 initobj
  • 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 new calls, it just initializes the data over the top of the old value - it doesn't allocate 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.

我希望这表明多么复杂的话题,而闪亮的一点光就可以在同一时间。在部分的概念感觉,每次调用分配堆栈空间 - 但正如我们所看到的,这不是真正发生的事情,即使在白细胞介素的水平。我想强调一个特定的情况下。采取这种方式:

I hope this shows how complicated the topic is, while shining a bit of light on it at the same time. In some conceptual senses, every call to new allocates 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:

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

这是逻辑有4个堆栈分配 - 一个用于变量,一个为三个要求的 - 但实际上(对于特定的code)该相同的存储位置被重新使用堆栈仅分配一次,然后

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

编辑:只是要清楚,这只是在某些情况下,真正的......尤其是价值的GUID 将不可见,如果的Guid 构造函数抛出异常,这就是为什么C#编译器是能够重复使用相同的栈槽。见埃里克利珀的<一个href="http://blogs.msdn.com/b/ericlippert/archive/2010/10/11/debunking-another-myth-about-value-types.aspx">blog岗位价值型建筑了解详细信息和情况下,它的的申请。

Just to be clear, this is only true in some cases... in particular, the value of guid won't be visible if the Guid constructor 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 construction for more details and a case where it doesn't apply.

我在写这个答案学到了很多东西 - 请要求澄清是否有任何目前还不清楚

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

这篇关于是否使用&QUOT;新&QUOT;在一个结构它分配在堆或栈?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆