c派生类对象的内存布局

北京哪里治疗白癜风效果最好 https://m.39.net/disease/a_a9vfyka.html

派生类对象的内存布局需满足的要求是,一个基类指针,无论其指向基类对象,还是派生类对象,通过它来访问一个基类中定义的数据成员,都可以用相同的步骤。理解了这一要求,就能理解下面介绍的派生类内存布局的合理性了。对象的内存布局问题,并不是C+标准中明确规定的,不同的编译器可以有不同的实现。我介绍一种很多编译器在使用的、最为自然的和容易理解的对象内存布局方式。

1.单继承的情况

单继承的情况比较简单。考虑下面的情况:

classBase{......};

classDerived:publicBase{......};

那么在Derived类的对象中,Derived从Base继承来的数据成员,全部放在前面,与这些数据成员在Base类的对象中放置的顺序保持一致,Derived类新增的数据成员全部放在后面,如图所示。如果出现了从Derived指针到Base指针的隐含转换,例如:

Base*Ba=newBase();//基类指针指向派生类对象

Derived*De=newDerived();//派生类指针指向派生类对象

Base*Ba2=De;//派生类指针转换基类指针

单继承情况下的对象内存布局

在De赋给Ba2的过程中,指针值不需要改变。Ba和Ba2这两个Base类型的指针虽然指向的对象具有不同的类型,但任何一个Base数据成员到该对象首地址都具有相同的偏移量,因此使用Base指针Ba和Ba2访问Base类中定义的数据成员时,可以采用相同的方式,而无须考虑具体的对象类型。

2.多继承的情况

多继承的情况要比单继承稍微复杂一些,考虑下面这种情况:

classBase1{......};

classBase2{......};

classDerived:publicBase1,publicBase2{...};

Derived类继承了Basel类和Base2类,在Derived类的对象中,前面依次存放的是从Base类和Base2类继承而来的数据成员,其顺序与它们在Basel类和Basc2类的对象中放置的顺序保持一致,Derived类新增的数据放在它们的后面,如图所示。Base如果出现了从Derived指针到Basel指针或Base2指针的隐含转换,例如:

Base1*Ba1a=newBase1();

Base2*Ba2a=newBase2();

DerivedDe=newDerived();

Base*Ba1b=De;

Base*Ba2b=De;

多继承情况下的对象内存布局

将pd赋给pblb指针时,与单继承时的情形相似,只需要把地址复制一遍即可。但将pd赋给pb2b指针时,则不能简单地执行地址复制操作,而应当在原地址的基础上加一个偏移量,使pb2b指针指向Derived对象中Base2类的成员的首地址。这样,对于同为Base2类型指针的pb2a和pb2b来说,它们都指向Base2中定义的、以相同方式分布的数据成员。

虚拟继承的情况

虚拟继承的情况更加复杂,考虑下面的继承关系

classBase0{...};

classBase1:virtualpublicBase0{...};

classBase2:virtualpublicBase0{...};

classDerived:publicBase1,publicBase2{...};

Basel类型指针和Base2类型指针都可以指向Derived对象,而且通过这两类指针都可以访问Base0类中定义的数据成员,但这些数据成员在Derived对象中只有一份。因此,只能通过间接的方式来确定Basel对象、Base2对象和Derived对象中Base0数据成员的位置。具体的解决办法因编译器而异,一种比较容易理解的布局方式是,在Basel类型对象和Base2类型对象中都增加一个隐含的指针,这个指针指向Base0中定义的数据成员的首地址。Derived类同时继承了这两枚指针,但由于Derived类中的Bac0类数据成员只有一份,因此Derived类型对象中的这两个隐含指针指向相同的地址。因为继承了指向Base0类数据成员的指针,Base0数据成员放置的位置已经不重要了,一般来说可以放在最后。如图所示:

虚拟继承下的对象内存布局(只是一种实

通过上面的讨论,我们还得到了一个收获:指针转换时并非都保持原先的地址不变,地址的算术运算有可能在指针转换时发生。能看到文章结尾的都是好孩子,感谢大家,喜欢我的文章请


转载请注明:http://www.aierlanlan.com/rzfs/7539.html