c++对象内存布局模型

转自:点击打开链接

首先介绍一下C++中有继承关系的类对象内存的布局: 

在C++中,如果类中有虚函数,那么它就会有一个虚函数表的指针__vfptr,在类对象最开始的内存数据中。之后是类中的成员变量的内存数据。 
对于子类,最开始的内存数据记录着父类对象的拷贝(包括父类虚函数表指针和成员变量)。 之后是子类自己的成员变量数据。 
对于子类的子类,也是同样的原理。但是无论继承了多少个子类,对象中始终只有一个虚函数表指针。 
 

 
 
为了探讨C++类对象的内存布局,先来写几个类和函数 
首先写一个基类: 
class Base 

public: 
virtual void f() { cout << "Base::f" << endl; } 
virtual void g() { cout << "Base::g" << endl; } 
virtual void h() { cout << "Base::h" << endl; } 
int base; 
protected: 
private: 
}; 
然后,我们多种不同的继承情况来研究子类的内存对象结构。 
1. 无虚函数集继承 
 
//子类1,无虚函数重载 
class Child1 : public Base 

public: 
virtual void f1() { cout << "Child1::f1" << endl; } 
virtual void g1() { cout << "Child1::g1" << endl; } 
virtual void h1() { cout << "Child1::h1" << endl; } 
int child1; 
protected: 
private: 
}; 
这个子类Child1没有继承任何一个基类的虚函数,因此它的虚函数表如下图: 
 
 
我们可以看出,子类的虚函数表中,先存放基类的虚函数,在存放子类自己的虚函数。 
 
2. 有一个虚函数继承 
//子类2,有1个虚函数重载 
class Child2 : public Base 

public: 
virtual void f() { cout << "Child2::f" << endl; } 
virtual void g2() { cout << "Child2::g2" << endl; } 
virtual void h2() { cout << "Child2::h2" << endl; } 
int child2; 
protected: 
private: 
}; 
 
当子类重载了父类的虚函数,则编译器会将子类虚函数表中对应的父类的虚函数替换成子类的函数。 
3. 全部虚函数都继承 
//子类3,全部虚函数重载 
class Child3 : public Base 

public: 
virtual void f() { cout << "Child3::f" << endl; } 
virtual void g() { cout << "Child3::g" << endl; } 
virtual void h() { cout << "Child3::h" << endl; } 
protected: 
int x; 
private: 
}; 
 
 
 
4. 多重继承 
多重继承,即类有多个父类,这种情况下的子类的内存结构和单一继承有所不同。 
 
我们可以看到,当子类继承了多个父类,那么子类的内存结构是这样的: 
子类的内存中,顺序 
 
5. 菱形继承 
 
 
6. 单一虚拟继承 
 
 
虚拟继承的子类的内存结构,和普通继承完全不同。
虚拟继承的子类,有单独的虚函数表, 另外也单独保存一份父类的虚函数表, 两部分之间用一个四个字节的0x00000000来作为分界。子类的内存中,首先是自己的虚函数表,然后是子类的数据成员,然后是0x0,之后就是父类的虚函数表,之后是父类的数据成员。 
如果子类没有自己的虚函数,那么子类就不会有虚函数表,但是子类数据和父类数据之间,还是需要0x0来间隔。 

因此,在虚拟继承中,子类和父类的数据,是完全间隔的,先存放子类自己的虚函数表和数据,中间以0x分界,最后保存父类的虚函数和数据。如果子类重载了父类的虚函数,那么则将子类内存中父类虚函数表的相应函数替换。 



 
7. 菱形虚拟继承

 
结论: 
(1) 对于基类,如果有虚函数,那么先存放虚函数表指针,然后存放自己的数据成员;如果没有虚函数,那么直接存放数据成员。 
(2) 对于单一继承的类对象,先存放父类的数据拷贝(包括虚函数表指针),然后是本类的数据。 
(3) 虚函数表中,先存放父类的虚函数,再存放子类的虚函数 
(4) 如果重载了父类的某些虚函数,那么新的虚函数将虚函数表中父类的这些虚函数覆盖。 
(5) 对于多重继承,先存放第一个父类的数据拷贝,在存放第二个父类的数据拷贝,一次类推,最后存放自己的数据成员。其中每一个父类拷贝都包含一个虚函数表指针。如果子类重载了某个父类的某个虚函数,那么该将该父类虚函数表的函数覆盖。另外,子类自己的虚函数,存储于第一个父类的虚函数表后边部分。 

(6) 当对象的虚函数被调用是,编译器去查询对象的虚函数表,找到该函数,然后调用。


关于虚继承的一些总结

转自:点击打开链接

1.为什么要引入虚拟继承

虚拟继承是多重继承中特有的概念。虚拟基类是为解决多重继承而出现的。如:类D继承自类B1、B2,而类B1、B2都继承自类A,因此在类D中两次出现类A中的变量和函数。为了节省内存空间,可以将B1、B2对A的继承定义为虚拟继承,而A就成了虚拟基类。实现的代码如下:

class A

class B1:public virtual A;

class B2:public virtual A;

class D:public B1,public B2;

虚拟继承在一般的应用中很少用到,所以也往往被忽视,这也主要是因为在C++中,多重继承是不推荐的,也并不常用,而一旦离开了多重继承,虚拟继承就完全失去了存在的必要因为这样只会降低效率和占用更多的空间。

 

2.引入虚继承和直接继承会有什么区别呢

由于有了间接性和共享性两个特征,所以决定了虚继承体系下的对象在访问时必然会在时间和空间上与一般情况有较大不同。

2.1时间:在通过继承类对象访问虚基类对象中的成员(包括数据成员和函数成员)时,都必须通过某种间接引用来完成,这样会增加引用寻址时间(就和虚函数一样),其实就是调整this指针以指向虚基类对象,只不过这个调整是运行时间接完成的。

2.2空间:由于共享所以不必要在对象内存中保存多份虚基类子对象的拷贝,这样较之多继承节省空间。虚拟继承与普通继承不同的是,虚拟继承可以防止出现diamond继承时,一个派生类中同时出现了两个基类的子对象。也就是说,为了保证这一点,在虚拟继承情况下,基类子对象的布局是不同于普通继承的。因此,它需要多出一个指向基类子对象的指针。

 

3.笔试,面试中常考的C++虚拟继承的知识点

第一种情况:         第二种情况:          第三种情况            第四种情况:
class a           class a              class a              class a
{              {                {                 {
    virtual void func();      virtual void func();       virtual void func();        virtual void func();
};              };                  char x;              char x;
class b:public virtual a   class b :public a           };                };
{              {                class b:public virtual a      class b:public a
    virtual void foo();        virtual void foo();     {                 {
};              };                  virtual void foo();        virtual void foo();
                               };                };

如果对这四种情况分别求sizeof(a),  sizeof(b)。结果是什么样的呢?下面是输出结果:(在vc6.0中运行)
第一种:4,12 
第二种:4,4
第三种:8,16
第四种:8,8

想想这是为什么呢?(详见上面的配图)

因为每个存在虚函数的类都要有一个4字节的指针指向自己的虚函数表,所以每种情况的类a所占的字节数应该是没有什么问题的,那么类b的字节数怎么算呢?看“第一种”和“第三种”情况采用的是虚继承,那么这时候就要有这样的一个指针vbptr,这个指针叫虚基类指针,也是四个字节;还要包括类a的字节数,所以类b的字节数就求出来了。而“第二种”和“第四种”情况则不包括vbptr这个指针,这回应该木有问题了吧。具体内容可以详见我的另一篇文章:点击打开链接


猜你喜欢

转载自blog.csdn.net/zjcxhswill/article/details/50677148