在C#中使用带有`new`运算符的值类型的困境

时间:2011-04-06 08:53:19

标签: c# .net value-type reference-type

operator new()与引用类型一起使用时,实例的空间在堆上分配,引用变量本身放在堆栈上。除此之外,在堆上分配的引用类型实例中的所有内容都将被清零 例如,这是一个类:

class Person
{
    public int id;
    public string name;
}

在以下代码中:

class PersonDemo
{
    static void Main()
    {
        Person p = new Person();
        Console.WriteLine("id: {0}  name: {1}", p.id, p.name);
    }
}

p变量在堆栈上,并且创建的Person实例(所有它的成员)都在堆上。 p.id0p.namenull。 这就是这种情况,因为在堆上分配的所有内容都已清零。

现在我感到困惑的是,如果我使用的是new运算符的值类型。例如,考虑以下结构:

struct Date
{
    public int year;
    public int month;
    public int day;
}

class DateDemo
{
    static void Main()
    {
        Date someDate;
        someDate= new Date();

        Console.WriteLine("someDate is: {0}/{1}/{2}", 
            someDate.month, someDate.day, someDate.year);
    }
}

现在我想知道main的以下几行:

        Date someDate;
        someDate= new Date();

在第一行someDate变量在堆栈上分配。准确地说是12个字节  我的问题是第二行会发生什么? operator new()做了什么?它是否仅将Date结构的成员清空,或者它还在堆上分配空间?一方面我不希望new在堆上分配空间,当然因为在第一行中已经在堆栈中为结构实例分配了内存。另一方面,我希望new在堆上分配空间并返回该空间的地址,因为这是new应该做的事情。 也许这是因为我来自C ++背景。

然而,如果答案是:“当new与值类型一起使用时,它只会将对象的成员清零”,而不是new运算符的含义不一致,因为:

  1. new与值类型一起使用时,它只会将堆栈中对象的成员清零
  2. new与引用类型一起使用时,它会在堆上为实例分配内存并使其成员为zerous-out
  3. 提前致谢,
    干杯

5 个答案:

答案 0 :(得分:20)

答案 1 :(得分:5)

好的,这是一个简单的:

class Program
{
    static void Main(string[] args)
    {
        DateTime dateTime = new DateTime();
        dateTime = new DateTime();
        Console.Read();
    }
}

编译到这个IL代码:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       24 (0x18)
  .maxstack  1
  .locals init ([0] valuetype [mscorlib]System.DateTime dateTime)
  IL_0000:  nop
  IL_0001:  ldloca.s   dateTime
  IL_0003:  initobj    [mscorlib]System.DateTime
  IL_0009:  ldloca.s   dateTime
  IL_000b:  initobj    [mscorlib]System.DateTime
  IL_0011:  call       int32 [mscorlib]System.Console::Read()
  IL_0016:  pop
  IL_0017:  ret
} // end of method Program::Main

正如您所看到的,CLR将使用相同的局部变量来存储新的值类型,尽管它将再次运行构造函数 - ,这很可能只是将内存归零。我们无法看到initobj是什么,这是 CLR实施

现实是,正如Eric Lippert解释here没有关于在堆栈上分配的值类型的一般规则。这完全取决于CLR的实现。

答案 2 :(得分:2)

struct的默认构造函数返回一个结构,其中所有内存都归零。也就是说,new SomeStruct()default(SomeStruct)相同。

然后,您的代码会将该默认结构分配给您的变量。

这就是你所知道的。

编译器如何实现这一点完全是编译器业务。

但是如果你对幕后很好奇,编译器很可能只是直接清除该变量的堆栈位置:假设变量存储在堆栈中。有许多事情可以阻止这种情况 - 一个例子是访问它的匿名函数,即:

Func<Person> PersonFactory()
{
  Person p = new Person();
  return () => p;
}

这里p需要存储在堆上才能在函数返回等时存在,因此new Person()将清除该堆位置。

反正。与C / C ++不同,使用C#,忘记“堆栈”,“堆”等是一个好主意.AFAIK,语言规范没有任何一个概念 - 它们都是特定的实现。谁知道,在逃避分析允许的情况下,某些未来的实现可能会在堆栈上放置一些堆值以节省GC一些工作量。最好不要针对特定​​的C#规范实现做出设计决策。

答案 3 :(得分:1)

从开发人员的角度来看,您不知道它的分配位置。例如,具有CLR的奇异设备,其不知道堆栈 - &gt; everthing在堆上。即使您考虑桌面CLR,在某些情况下JITer也可以将变量从堆栈移动到堆中。

More info.

答案 4 :(得分:0)

关于结构的归零。

无参数构造函数将成员归零。

如果您不使用new(),则无法访问struct成员,除非您首先自行初始化它们。否则你将获得“使用可能未分配的字段”。