当使用new操作符创建类的实例时,将在堆上分配内存。当你用new操作符创建一个结构体实例时,内存分配在哪里,在堆上还是在堆栈上?


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

有关何时使用结构体的更多细节,请参阅此处的问题。 这个问题是关于结构体的更多信息。

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


结构体被分配给堆栈。下面是一个有用的解释:

结构体

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


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

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

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


很多被认为是值类型的结构体都是在堆栈上分配的,而对象是在堆上分配的,而对象引用(指针)是在堆栈上分配的。


好吧,看看我能不能说得更清楚些。

首先,Ash是对的:问题不在于值类型变量分配在哪里。这是一个不同的问题——这个问题的答案不仅仅是“在堆栈上”。它比这要复杂得多(c# 2让它变得更加复杂)。我有一篇关于这个主题的文章,如果需要的话,我会进一步展开,但让我们只讨论新的操作符。

其次,所有这些都取决于你所谈论的水平。我正在查看编译器对源代码做了什么,根据它创建的IL。JIT编译器很有可能在优化大量“逻辑”分配方面做一些聪明的事情。

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

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


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#来说,将“用零初始化一个值”作为构造函数是有意义的,因为它保持了语言的一致性——你可以把new(…)看作总是调用构造函数。对于CLI来说,从不同的角度考虑它是有意义的,因为没有真正的代码要调用——当然也没有特定于类型的代码。

在初始化后对值的处理也会有所不同。用于

Guid localVariable = new Guid(someString);

不同于用于:

myInstanceOrStaticVariable = new Guid(someString);

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

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: 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.

我希望这能显示出这个话题有多复杂,同时也能让人对它有所了解。从某种概念上讲,每次调用new都会在堆栈上分配空间——但正如我们所看到的,即使在IL级别上也不是这样。我想强调一个特殊的案例。试试这个方法:

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

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

编辑:澄清一下,这只是在某些情况下是正确的……特别是,如果guid构造函数抛出异常,guid的值将不可见,这就是c#编译器能够重用相同堆栈槽的原因。请参阅Eric Lippert关于价值类型构建的博客文章,了解更多细节以及它不适用的情况。

我在写这个答案中学到了很多——如果有任何不清楚的地方,请澄清!


简单地说,new是struct的用词不当,调用new只是调用构造函数。结构体的唯一存储位置是定义它的位置。

如果它是成员变量,它将直接存储在定义它的任何地方,如果它是局部变量或参数,它将存储在堆栈中。

与此形成对比的是类,类在结构完整存储的任何位置都有引用,而引用点则位于堆上的某个位置。(Member within, local/parameter on stack)

研究一下c++可能会有所帮助,在c++中,类/结构之间没有真正的区别。(在语言中有类似的名字,但它们只指事物的默认可访问性)当你调用new时,你会得到一个指向堆位置的指针,而如果你有一个非指针引用,它会直接存储在堆栈上或其他对象中,就像c#中的结构体一样。


我可能遗漏了一些东西但是我们为什么要关心分配呢?

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

引用类型是通过引用传递的,可以进行突变。

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

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


类或结构声明就像一个蓝图,用于在运行时创建实例或对象。如果定义一个名为Person的类或结构,则Person是类型的名称。如果声明并初始化一个Person类型的变量p,则p是Person的对象或实例。可以创建同一Person类型的多个实例,并且每个实例的属性和字段可以具有不同的值。

类是引用类型。创建类的对象时,赋给该对象的变量仅保存对该内存的引用。当对象引用被赋值给一个新变量时,新变量引用原始对象。通过一个变量所做的更改会反映在另一个变量中,因为它们都引用相同的数据。

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

一般来说,类被用来建模更复杂的行为,或者在创建类对象后要修改的数据。结构最适合于小型数据结构,这些数据主要包含在结构创建后不打算修改的数据。

更多的……