C++ 对象的内存布局(上)

前言

 
0712月,我写了一篇《 C++虚函数表解析 》的文章,引发了你们的兴趣。有不少朋友对个人文章留了言,有鼓励个人,有批评个人,还有不少问问题的。我在这里一并对你们的留言表示感谢。这也是我为何再写一篇续言的缘由。由于,在上一篇文章中,我用了的示例都是很是简单的,主要是为了说明一些机理上的问题,也是为了图一些表达上方便和简单。不想,这篇文章成为了打开C++对象模型内存布局的一个引子,引起了你们对C++对象的更深层次的讨论。固然,我以前的文章还有不少方面没有涉及,从我我的感受下来,在谈论虚函数表里,至少有如下这些内容没有涉及:

1)有成员变量的状况。
2)有重复继承的状况。
3)有虚拟继承的状况。
4)有钻石型虚拟继承的状况。
 
这些都是我本篇文章须要向你们说明的东西。因此,这篇文章将会是《 C++虚函数表解析 》的一个续篇,也是一篇高级进阶的文章。我但愿你们在读这篇文章以前对C++有必定的基础和了解,并能先读个人上一篇文章。由于这篇文章的深度可能会比较深,并且会比较杂乱,我但愿你在读本篇文章时不会有大脑思惟紊乱致使大脑死机的状况。;-)
 

对象的影响因素

 
简而言之,咱们一个类可能会有以下的影响因素:
 
1)成员变量
2)虚函数(产生虚函数表)
3)单一继承(只继承于一个类)
4)多重继承(继承多个类)
5)重复继承(继承的多个父类中其父类有相同的超类)
6)虚拟继承(使用virtual方式继承,为了保证继承后父类的内存布局只会存在一份)
上述的东西一般是C++这门语言在语义方面对对象内部的影响因素,固然,还会有编译器的影响(好比优化),还有字节对齐的影响。在这里咱们都不讨论,咱们只讨论C++语言上的影响。
 
本篇文章着重讨论下述几个状况下的C++对象的内存布局状况。
 
1)单一的通常继承(带成员变量、虚函数、虚函数覆盖)
2)单一的虚拟继承(带成员变量、虚函数、虚函数覆盖)
3)多重继承(带成员变量、虚函数、虚函数覆盖)
4)重复多重继承(带成员变量、虚函数、虚函数覆盖)
5)钻石型的虚拟多重继承(带成员变量、虚函数、虚函数覆盖)
 
咱们的目标就是,让事情愈来愈复杂。
 

知识复习

 
咱们简单地复习一下,咱们能够经过对象的地址来取得虚函数表的地址,如:
 
          typedef void(*Fun)(void);
 
            Base b;
 
            Fun pFun = NULL;
 
            cout << " 虚函数表地址: " << (int*)(&b) << endl;
            cout << " 虚函数表 第一个函数地址: " << (int*)*(int*)(&b) << endl;
 
            // Invoke the first virtual function 
            pFun = (Fun)*((int*)*(int*)(&b));
            pFun();
 
咱们一样能够用这种方式来取得整个对象实例的内存布局。由于这些东西在内存中都是连续分布的,咱们只须要使用适当的地址偏移量,咱们就能够得到整个内存对象的布局。
 
本篇文章中的例程或内存布局主要使用以下编译器和系统:
1)Windows XP VC++ 2003
2)Cygwin G++ 3.4.4
 

单一的通常继承

 
下面,咱们假设有以下所示的一个继承关系:
 
 
请注意,在这个继承关系中,父类,子类,孙子类都有本身的一个成员变量。而了类覆盖了父类的f()方法,孙子类覆盖了子类的g_child()及其超类的f()
 
咱们的源程序以下所示:
 
class Parent {
public :
    int iparent;
    Parent ():iparent (10) {}
    virtual void f() { cout << " Parent::f()" << endl; }
    virtual void g() { cout << " Parent::g()" << endl; }
    virtual void h() { cout << " Parent::h()" << endl; }
 
};
 
class Child : public Parent {
public :
    int ichild;
    Child():ichild(100) {}
    virtual void f() { cout << "Child::f()" << endl; }
    virtual void g_child() { cout << "Child::g_child()" << endl; }
    virtual void h_child() { cout << "Child::h_child()" << endl; }
};
 
class GrandChild : public Child{
public :
    int igrandchild;
    GrandChild():igrandchild(1000) {}
    virtual void f() { cout << "GrandChild::f()" << endl; }
    virtual void g_child() { cout << "GrandChild::g_child()" << endl; }
    virtual void h_grandchild() { cout << "GrandChild::h_grandchild()" << endl; }
};
咱们使用如下程序做为测试程序:(下面程序中,我使用了一个int** pVtab 来做为遍历对象内存布局的指针,这样,我就能够方便地像使用数组同样来遍历全部的成员包括其虚函数表了,在后面的程序中,我也是用这样的方法的,请没必要感到奇怪,)
 
    typedef void(*Fun)(void);

    GrandChild gc;
   
 
    int** pVtab = (int**)&gc;
 
    cout << "[0] GrandChild::_vptr->" << endl;
    for (int i=0; (Fun)pVtab[0][i]!=NULL; i++){
                pFun = (Fun)pVtab[0][i];
                cout << "    ["<<i<<"] ";
                pFun();
    }
    cout << "[1] Parent.iparent = " << (int)pVtab[1] << endl;
    cout << "[2] Child.ichild = " << (int)pVtab[2] << endl;
    cout << "[3] GrandChild.igrandchild = " << (int)pVtab[3] << endl;
 
其运行结果以下所示:(在VC++ 2003G++ 3.4.4下)
 
[0] GrandChild::_vptr->
    [0] GrandChild::f()
    [1] Parent::g()
    [2] Parent::h()
    [3] GrandChild::g_child()
    [4] Child::h1()
    [5] GrandChild::h_grandchild()
[1] Parent.iparent = 10
[2] Child.ichild = 100
[3] GrandChild.igrandchild = 1000
 
使用图片表示以下:
 
 
 
 
可见如下几个方面:
1)虚函数表在最前面的位置。
2)成员变量根据其继承和声明顺序依次放在后面。
3)在单一的继承中,被overwrite的虚函数在虚函数表中获得了更新。
 
 
 
 
 

多重继承

 
下面,再让咱们来看看多重继承中的状况,假设有下面这样一个类的继承关系。注意:子类只overwrite了父类的f()函数,而还有一个是本身的函数(咱们这样作的目的是为了用g1()做为一个标记来标明子类的虚函数表)。并且每一个类中都有一个本身的成员变量:
 
 
 
 
咱们的类继承的源代码以下所示:父类的成员初始为102030,子类的为100
 
class Base1 {
public :
    int ibase1;
    Base1():ibase1(10) {}
    virtual void f() { cout << "Base1::f()" << endl; }
    virtual void g() { cout << "Base1::g()" << endl; }
    virtual void h() { cout << "Base1::h()" << endl; }
 
};
 
class Base2 {
public :
    int ibase2;
    Base2():ibase2(20) {}
    virtual void f() { cout << "Base2::f()" << endl; }
    virtual void g() { cout << "Base2::g()" << endl; }
    virtual void h() { cout << "Base2::h()" << endl; }
};
 
class Base3 {
public :
    int ibase3;
    Base3():ibase3(30) {}
    virtual void f() { cout << "Base3::f()" << endl; }
    virtual void g() { cout << "Base3::g()" << endl; }
    virtual void h() { cout << "Base3::h()" << endl; }
};
 
 
class Derive : public Base1, public Base2, public Base3 {
public :
    int iderive;
    Derive():iderive(100) {}
    virtual void f() { cout << "Derive::f()" << endl; }
    virtual void g1() { cout << "Derive::g1()" << endl; }
};
 
咱们经过下面的程序来查看子类实例的内存布局:下面程序中,注意我使用了一个s变量,其中用到了sizof(Base)来找下一个类的偏移量。(由于我声明的是int成员,因此是4个字节,因此没有对齐问题。关于内存的对齐问题,你们能够自行试验,我在这里就很少说了)
 
             typedef void(*Fun)(void);

               Derive d;
 
                int** pVtab = (int**)&d;
 
                cout << "[0] Base1::_vptr->" << endl;
                pFun = (Fun)pVtab[0][0];
                cout << "     [0] ";
                pFun();
 
                pFun = (Fun)pVtab[0][1];
                cout << "     [1] ";pFun();
 
                pFun = (Fun)pVtab[0][2];
                cout << "     [2] ";pFun();
 
                pFun = (Fun)pVtab[0][3];
                cout << "     [3] "; pFun();
 
                pFun = (Fun)pVtab[0][4];
                cout << "     [4] "; cout<<pFun<<endl;
 
                cout << "[1] Base1.ibase1 = " << (int)pVtab[1] << endl;
 
 
                int s = sizeof(Base1)/4;
 
                cout << "[" << s << "] Base2::_vptr->"<<endl;
                pFun = (Fun)pVtab[s][0];
                cout << "     [0] "; pFun();
 
                Fun = (Fun)pVtab[s][1];
                cout << "     [1] "; pFun();
 
                pFun = (Fun)pVtab[s][2];
                cout << "     [2] "; pFun();
 
                pFun = (Fun)pVtab[s][3];
                out << "     [3] ";
                cout<<pFun<<endl;
 
                cout << "["<< s+1 <<"] Base2.ibase2 = " << (int)pVtab[s+1] << endl;
 
                s = s + sizeof(Base2)/4;

                cout << "[" << s << "] Base3::_vptr->"<<endl;
                pFun = (Fun)pVtab[s][0];
                cout << "     [0] "; pFun();
 
                pFun = (Fun)pVtab[s][1];
                cout << "     [1] "; pFun();
 
                pFun = (Fun)pVtab[s][2];
                cout << "     [2] "; pFun();
 
                pFun = (Fun)pVtab[s][3];
                 cout << "     [3] ";
                cout<<pFun<<endl;
 
                s++;
                cout << "["<< s <<"] Base3.ibase3 = " << (int)pVtab[s] << endl;
                s++;
                cout << "["<< s <<"] Derive.iderive = " << (int)pVtab[s] << endl;
 
其运行结果以下所示:(在VC++ 2003G++ 3.4.4下)

[0] Base1::_vptr->
     [0] Derive::f()
     [1] Base1::g()
     [2] Base1::h()
     [3] Driver::g1()
     [4] 00000000      ç 注意:在GCC下,这里是1
[1] Base1.ibase1 = 10
[2] Base2::_vptr->
     [0] Derive::f()
     [1] Base2::g()
     [2] Base2::h()
     [3] 00000000      ç 注意:在GCC下,这里是1
[3] Base2.ibase2 = 20
[4] Base3::_vptr->
     [0] Derive::f()
     [1] Base3::g()
     [2] Base3::h()
     [3] 00000000
[5] Base3.ibase3 = 30
[6] Derive.iderive = 100

使用图片表示是下面这个样子:
 
 
 
咱们能够看到:
1)  每一个父类都有本身的虚表。
2)  子类的成员函数被放到了第一个父类的表中。
3)  内存布局中,其父类布局依次按声明顺序排列。
4)  每一个父类的虚表中的f()函数都被overwrite成了子类的f()。这样作就是为了解决不一样的父类类型的指针指向同一个子类实例,而可以调用到实际的函数。
相关文章
相关标签/搜索