栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

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

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

好吧,让我们看看是否可以更清楚地说明这一点。

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

new
运算符。

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

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

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


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

对于C#来说,将“用零初始化值”视为构造函数是有意义的,因为它使语言保持一致-您可以将其

new(...)
视为 始终
调用构造函数。对于CLI,以不同的方式考虑它是有意义的,因为没有要调用的实际代码-当然也没有特定于类型的代码。

初始化值后,对值的处理方式也会有所不同。用于的IL

Guid localVariable = new Guid(someString);

与用于以下方面的IL不同:

myInstanceOrStaticVariable = new Guid(someString);

此外,如果将该值用作中间值(例如,方法调用的参数),则情况会稍有不同。为了显示所有这些差异,这是一个简短的测试程序。它没有显示出静态变量和实例变量之间的区别: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());    }}

这是该类的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
    :使用已分配的存储位置(无论是否在堆栈上)。在上面的代码中使用它来分配给局部变量。如果使用多个
    new
    调用多次为同一个局部变量分配一个值,则它只会在旧值的顶部初始化数据-它 不会 每次分配更多的堆栈空间。
  • initobj
    :使用已分配的存储位置,仅擦除数据。这用于我们所有的无参数构造函数调用,包括那些分配给局部变量的调用。对于方法调用,有效地引入了一个中间局部变量,其值被擦除了
    initobj

我希望这可以显示该主题的复杂性,同时又可以给它带来一些启发。从 某些 概念上讲,每次调用都会

new
在堆栈上分配空间-
但正如我们所看到的,即使在IL级别,这也不是真正发生的情况。我想强调一个特殊情况。采取这种方法:

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

“逻辑上”有4个堆栈分配-一个用于变量,一个用于三个

new
调用中的每一个-但实际上(对于该特定代码)该堆栈仅分配一次,然后重用相同的存储位置。

编辑:请清楚一点,这仅在某些情况下是正确的…特别是,

guid
如果
Guid
构造函数抛出异常,则w
的值将不可见,这就是C#编译器能够重用同一堆栈槽的原因。请参阅Eric Lippert
关于值类型构造的博客文章,以了解更多详细信息和 适用的情况。

我在编写此答案时学到了很多东西-如果不清楚,请要求澄清!



转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/368541.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号