`
252190908
  • 浏览: 227993 次
文章分类
社区版块
存档分类
最新评论

C++ 对象的内存布局深层剖析(上)

 
阅读更多

对象的影响因素

简而言之,我们一个类可能会有如下的影响因素:

1)成员变量

2)虚函数(产生虚函数表)

3)单一继承(只继承于一个类)

4)多重继承(继承多个类)

5)重复继承(继承的多个父类中其父类有相同的超类)

6)虚拟继承(使用virtual方式继承,为了保证继承后父类的内存布局只会存在一份)

上述的东西通常是C++这门语言在语义方面对对象内部的影响因素,当然,还会有编译器的影响(比如优化),还有字节对齐的影响。在这里我们都不讨论,我们只讨论C++语言上的影响。

本篇文章着重讨论下述几个情况下的C++对象的内存布局情况。

1)单一的一般继承(带成员变量、虚函数、虚函数覆盖)

2)单一的虚拟继承(带成员变量、虚函数、虚函数覆盖)

3)多重继承(带成员变量、虚函数、虚函数覆盖)

4)重复多重继承(带成员变量、虚函数、虚函数覆盖)

5)钻石型的虚拟多重继承(带成员变量、虚函数、虚函数覆盖)

我们的目标就是,让事情越来越复杂。

简单地复习一下,我们可以通过对象的地址来取得虚函数表的地址

可以参考上一篇:c++虚函数表解析

我们同样可以用这种方式来取得整个对象实例的内存布局。因为这些东西在内存中都是连续分布的,我们只需要使用适当的地址偏移量,我们就可以获得整个内存对象的布局。

下面,我们假设有如下所示的一个继承关系:

请注意,在这个继承关系中,父类,子类,孙子类都有自己的一个成员变量。而了类覆盖了父类的f()方法,孙子类覆盖了子类的g_child()及其超类的f()。

我们的源程序如下所示:


/*
 * =====================================================================================
 *
 *       Filename:  parent.cpp
 *    Description:  c++ 对象的内存布局单一继承测试 
 *        Created:  2013年11月25日 19时20分02秒
 *       Revision:  none
 *       Compiler:  gcc
 *         Author:  max_min_ 
 * =====================================================================================
 */
#include <iostream>
using namespace std;

class CParent 
{
    public:
        CParent():iparent(10)
        {   
        }   

        virtual void f() 
        {   
            cout << "CParent::f()" << endl;
        }   

        virtual void g() 
        {   
            cout << "CParent::g()" << endl;
        }   

        virtual void h() 
        {   
            cout << "CParent::h()" << endl; 
        }   
    public:
        int iparent;
};
class CChild:public CParent
{
    public:
        CChild():ichild(100)
        {
        }

        virtual void f()
        {
            cout << "CChild::f()" << endl;
        }

        virtual void  g_child()
        {
            cout << "CChild::g_child()" << endl;
        }

        virtual void h_child()
        {
            cout << "CChild::h_child()" << endl;
        }

    public:
        int ichild;
};
class CGrandchild:public CChild
{
    public:
        CGrandchild():igrandchild(1000)
        {
        }

        virtual void f()
        {
            cout << "CGrandchild::f()" << endl;
        }

        virtual void g_child()
        {
            cout << "CGrandchild::g_child()" << endl;
        }

        virtual void h_grandchild()
        {
            cout << "CGrandchild::h_grandchild()" << endl;
        }
    public:
    int igrandchild;
};
typedef void (*CallbackFunc)(void );   // Function pointer

int main(void)
{
    CallbackFunc pFunc = NULL;

    CGrandchild m_gc;

    int **pVtab = (int **)&m_gc;
    cout << "[0] CGrandchild::_vptr->" << endl;
    for(int i= 0; (CallbackFunc)pVtab[0][i] != NULL ; i++)
    {
        pFunc = (CallbackFunc)pVtab[0][i];
        cout << "         [" << i << "]  ";
        pFunc();
    }

    cout << endl;
    cout << "[1] parent-iparen          = " << (int) pVtab[1] << endl;
    cout << "[2] child-ichild           = " << (int) pVtab[2] << endl;
    cout << "[3] grandchild-igrandchild = " << (int) pVtab[3] << endl;

    return 0;
}

其运行结果如下所示:(gcc version 4.4.3 (Ubuntu 4.4.3-4ubuntu5.1) )

[0] CGrandchild::_vptr->
         [0]  CGrandchild::f()
         [1]  CParent::g()
         [2]  CParent::h()
         [3]  CGrandchild::g_child()
         [4]  CChild::h_child()
         [5]  CGrandchild::h_grandchild()

[1] parent-iparen          = 10
[2] child-ichild           = 100
[3] grandchild-igrandchild = 1000

使用图片表示如下:

可见以下几个方面:

1)虚函数表在最前面的位置。

2)成员变量根据其继承和声明顺序依次放在后面。

3)在单一的继承中,被overwrite的虚函数在虚函数表中得到了更新

多重继承

下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类只overwrite了父类的f()函数,而还有一个是自己的函数(我们这样做的目的是为了用g1()作为一个标记来标明子类的虚函数表)。而且每个类中都有一个自己的成员变量:

我们的类继承的源代码如下所示:父类的成员初始为10,20,30,子类的为100

我们通过下面的程序来查看子类实例的内存布局:下面程序中,注意我使用了一个s变量,其中用到了sizof(Base)来找下一个类的偏移量。(因为我声明的是int成员,所以是4个字节,所以没有对齐问题。关于内存的对齐问题,大家可以自行试验,我在这里就不多说了)源程序如下:

/*
 * =====================================================================================
 *
 *       Filename:  mulparent.cpp
 *    Description:  c++ 对象的内存布局多重继承测试  
 *        Version:  1.0
 *        Created:  2013年11月25日 19时58分54秒
 *       Revision:  none
 *       Compiler:  gcc
 *         Author:  max_mix_ 
 * =====================================================================================
 */
#include <iostream>
using namespace std;

class Base1
{
    public:
        Base1():ibase1(10)
        {   

        }   

        virtual void f() 
        {   
            cout << "Base1::f()" << endl;
        }   

        virtual void g() 
        {   
            cout << "Base1::g()" << endl;
        }   

        virtual void h() 
        {   
            cout << "Base1::h()" << endl;
        }   
    public:
        int ibase1;

};
class Base2
{
    public:
        Base2():ibase2(20)
        {

        }

        virtual void f()
        {
            cout << "Base2::f()" << endl;
        }

        virtual void g()
        {
            cout << "Base2::g()" << endl;
        }

        virtual void h()
        {
            cout << "Base2::h()" << endl;
        }
    public:
        int ibase2;

};
class Base3
{
    public:
        Base3():ibase3(30)
        {

        }

        virtual void f()
        {
            cout << "Base3::f()" << endl;
        }

        virtual void g()
        {
            cout << "Base3::g()" << endl;
        }

        virtual void h()
        {
            cout << "Base3::h()" << endl;
        }
    public:
        int ibase3;

};
class Derive:public Base1, public Base2, public Base3
{
    public:
        Derive():iderive(100)
    {
    }

        virtual void f()
        {
            cout << "Derive::f()" << endl;
        }

        virtual void g1()
        {
            cout << "Derive::g1()" << endl;
        }
    public:
        int iderive;
};

typedef void(*Fun)(void);   //Function pointer
int main(void )
{

    Derive d;
    Fun pFun = NULL;
    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::ibae1 = " << (int )pVtab[1] << endl;
    int s = sizeof(Base1)/4;
    cout << "["<< s << "] Base2::_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;
    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;

}
其运行结果如下所示:

[0] Base1::_vptr->
     [0] Derive::f()
     [1] Base1::g()
     [2] Base1::h()
     [3] Derive::g1()
     [4] 1       //注意:windows下 这里是0
[1] Base1::ibae1 = 10
[2] Base2::_vptr-> 
     [0] Derive::f()
     [1] Base2::g()
     [2] Base2::h()
     [3] 1             // 注意:windows下 这里是0
[3] Base2.ibase2 = 20
[4] Base3::_vptr->
     [0] Derive::f()
     [1] Base3::g()
     [2] Base3::h()
     [3] 0
[5] Base3.ibase3   = 30
[6] Derive.iderive = 100
使用图片表示是下面这个样子:

我们可以看到:

1) 每个父类都有自己的虚表。

2) 子类的成员函数被放到了第一个父类的表中。

3) 内存布局中,其父类布局依次按声明顺序排列。

4) 每个父类的虚表中的f()函数都被overwrite成了子类的f()。这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。


本文核心资料来自:http://blog.csdn.net/haoel/article/details/3081328






分享到:
评论

相关推荐

    Think in Java(中文版)chm格式

    1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段1:要制作什么? 1.12.4 阶段2:开始构建? 1.12.5 阶段3:正式创建 1.12.6 阶段4:校订 1.12.7 计划的回报 1.13 Java还是C++? ...

    JAVA_Thinking in Java

    1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段1:要制作什么? 1.12.4 阶段2:开始构建? 1.12.5 阶段3:正式创建 1.12.6 阶段4:校订 1.12.7 计划的回报 1.13 Java还是C++? 第2章 ...

    Java初学者入门教学

    1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段1:要制作什么? 1.12.4 阶段2:开始构建? 1.12.5 阶段3:正式创建 1.12.6 阶段4:校订 1.12.7 计划的回报 1.13 Java还是C++? 第2章 ...

    ThinkInJava

    1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段1:要制作什么? 1.12.4 阶段2:开始构建? 1.12.5 阶段3:正式创建 1.12.6 阶段4:校订 1.12.7 计划的回报 1.13 Java还是C++? 第2章 ...

    java 编程入门思考

    1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段1:要制作什么? 1.12.4 阶段2:开始构建? 1.12.5 阶段3:正式创建 1.12.6 阶段4:校订 1.12.7 计划的回报 1.13 Java还是C++? 第2章 ...

    thinkinjava

    1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段1:要制作什么? 1.12.4 阶段2:开始构建? 1.12.5 阶段3:正式创建 1.12.6 阶段4:校订 1.12.7 计划的回报 1.13 Java还是C++? 第2章 ...

    Thinking in Java简体中文(全)

    1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段1:要制作什么? 1.12.4 阶段2:开始构建? 1.12.5 阶段3:正式创建 1.12.6 阶段4:校订 1.12.7 计划的回报 1.13 Java还是C++? 第2章 ...

    java联想(中文)

    1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段1:要制作什么? 1.12.4 阶段2:开始构建? 1.12.5 阶段3:正式创建 1.12.6 阶段4:校订 1.12.7 计划的回报 1.13 Java还是C++? 第2章 ...

    Thinking in Java 中文第四版+习题答案

    1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段1:要制作什么? 1.12.4 阶段2:开始构建? 1.12.5 阶段3:正式创建 1.12.6 阶段4:校订 1.12.7 计划的回报 1.13 Java还是C++? 第2章 一切...

    Thinking in Java(中文版 由yyc,spirit整理).chm

    1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段1:要制作什么? 1.12.4 阶段2:开始构建? 1.12.5 阶段3:正式创建 1.12.6 阶段4:校订 1.12.7 计划的回报 1.13 Java还是C++? 第2章 ...

    JAVA_Thinking in Java(中文版 由yyc,spirit整理).chm

    1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段1:要制作什么? 1.12.4 阶段2:开始构建? 1.12.5 阶段3:正式创建 1.12.6 阶段4:校订 1.12.7 计划的回报 1.13 Java还是C++? 第2章 ...

Global site tag (gtag.js) - Google Analytics