子类的对象是否也分配了父类的对象?

时间:2017-06-08 18:54:27

标签: c# inheritance heap-memory allocation

Class A
{
    int X {get;set;}
}
Class B : A
{
    int Y {get;set;}
}

B myB;

为了论证而说,A类型的对象是4个字节,B类型的对象是4个字节。

B被分配/初始化/构造,其内容存储在堆上。

B和8的指针是串联的B 8字节的大小,还是两个4字节的对象?

是否仍会导致两个对象,例如以下情况?

Class A {}
Class B
{
    A myA;
}

B myB;

我可以假设,如果这些情况导致不同的数据,那么继承使用的堆分配比构造少,因为附加的包含对象已分配数据 - 并且堆将此数据分配到其包含的对象之外?

我可以断定构造的内存效率远低于继承吗?

澄清: 初始化子类类型的对象时,该对象的父类属性的字节是否与子类属性的字节一起存储? B是否存储指向A的指针或是否直接存储A的属性?如果它存储指向A实例的指针,那么它将使它在内存中的含义与它具有的属性相同改为输入A.

增加: 据说B的数据也包含第一个例子中的A的数据。 在分配子类对象的内存时是否有两个分配或一个分配?

在第二个例子中,你要分配两次内存:一次是针对类型A的包含属性对象,另一次针对对象类B,所以我假设你有两个不同的内存分配和两个不同的对象指向B内部的指针。

如果一个子类只为其所有属性的整个大小及其所有继承属性分配一次内存,并将所有这些信息内联存储,那么这不会使它成为比为一个对象分配多个对象更优越的内存分配方法?

2 个答案:

答案 0 :(得分:1)

假设您在x86架构上运行.NET,则空对象的开销为8个字节(最小大小为12个字节)。这意味着new object()将在内存中占用12个字节。

考虑以下类结构:

public class Parent // overhead 8 bytes
{
    int a, b, c, d; // int 4 bytes * 4 = 16 bytes
}

public class Child : Parent // inherits everything
{ }

var parent = new Parent(); // 24 bytes
var child = new Child(); // 24 bytes [NOTE: this is a SINGLE object of type Child, not two objects Parent+Child]

它们是相同大小的对象,因为Child未声明任何其他字段。

以下类结构:

public class Composable // overhead 8 bytes
{
    int a, b, c, d; // int 4 bytes * 4 = 16 bytes
}

public class Composite // overhead 8 bytes
{
    Composable c = new Composable(); // 4 bytes (reference on the heap)
}

var composable = new Composable(); // 24 bytes
var composite = new Composite(); // 36 bytes (24 + 12) [NOTE: this creates TWO different objects, with one referencing the other]

由于两个原因会产生稍高的开销:创建的新对象的“空对象”开销和为堆栈上的内部对象分配的引用。

回答你的问题:是的,组合需要更多的内存然后继承,但这些数字真的太小而不敢担心。

关于您的其他问题

  

初始化子类类型的对象时,该对象的父类属性的字节是否与子类属性的字节一起存储?

是的,正如继承示例所清楚显示的那样:我们有一个对象,包含其类和任何继承类的所有字段。

  

据说B的数据也包含第一个例子中的A的数据。在分配子类对象的内存时是否有两个分配或一个分配?

一次性分配

  

在第二个例子中,你要分配两次内存:一次是针对类型A的包含属性对象,另一次针对对象类B,所以我假设你有两个不同的内存分配和两个不同的对象指向B内部的指针。

你几乎是正确的,第二次分配只有在真的创建该对象时才会发生。在您的示例中,您只是声明一个字段A myA:这需要4个字节的内存(在x86中)而不是更多。创建该对象时会占用更多内存(例如A myA = new A()

  

如果一个子类只为其所有属性的整个大小及其所有继承属性分配一次内存,并将所有这些信息内联存储,那么这不会使它成为比为一个对象分配多个对象更优越的内存分配方法?

组合与继承具有不同的特征,我不相信它有任何现实世界的影响,但是,继承占用稍微的内存少于组合。

参考:OF MEMORY AND STRINGS来自C#...错误...我的意思是Jon Skeet。

答案 1 :(得分:1)

  

初始化子类类型的对象时,该对象的父类属性的字节是否与子类属性的字节一起存储?

是的,可能。关于内存模型,C#本身没有详细说明(如果有的话;我对规范不是很熟悉)。我很确定这是实现定义的。但是,包含对其Derived的完全独立实例的引用的Base实例由于以下几个原因而没有意义:

如果Base是抽象的,那么内存模型不仅允许但实际上需要创建抽象类的实例。

在IL级别,Derived的托管指针(实际引用)可以放在预期到Base的托管指针的任何位置。现在,我们可以排除运行时执行某种转换(例如,引用Base实例),因为这会丢失RTTI。但是我们知道我们可以在运行时将Base引用转换为Derived实例,因此它不能修改指针。

但是,当尝试通过Base的实例访问和修改Derived的字段时,这会引起皱纹。在这种情况下,CLR必须将字段信息添加到实例v表。当然不是不可能的,但在那时CLR实现者只会竭尽全力让事情变得更加困难。而且据我所知,以这种方式做事并没有什么好处(除非您希望能够在运行时更改继承层次结构,C#和CLR都不允许)。

通过在Base的实例中直接包含Derived的字段来添加另一层可以避免的间接性,这也会导致许多相当无意义的性能损失。