对象模型~

                                          (VC6实现的C++对象模型)

      

       C++对象模型中,类成员的分布情况:

1)        non-static 数据成员位于每个对象之中

2)        static 数据成员与所有的函数都位于对象之外

处理虚函数:

       类的所有虚函数地址放在一块,称之为虚拟函数表。

编译器为每个类对象插入一指针vptr 指向该虚函数表。

虚函数在函数表中的位置按一定的顺序,每个函数名对应着一个slot_id,调用的时候直接调用v-tableslot_id对应的函数。

上面是最基本的模型。至于virtual-base 多重继承 等的模型也是基于上述模型进行一些改变,具体等到后面再说~,先把简单的搞定了J

不考虑虚函数的数据布局

struct A

{

    void v_fun1(){};

    void v_fun2(){};

    int a;

    int b;

    int c;

    static int d;

};

main ()

{

    A a,b;

    cout<<FMT<<"sizeof(a) = "<<sizeof(a)<<endl;

    cout<<FMT<<"&a = "<<&a<<endl;

    cout<<FMT<<"&a.a = "<<&a.a<<endl;

    cout<<FMT<<"&a.b = "<<&a.b<<endl;

    cout<<FMT<<"&a.c = "<<&a.c<<endl;

    cout<<FMT<<"&b = "<<&b<<endl;

    cout<<FMT<<"&b.a = "<<&b.a<<endl;

    cout<<FMT<<"&b.b = "<<&b.b<<endl;

    cout<<FMT<<"&b.c = "<<&b.c<<endl;

    cout<<"/t——————————"<<endl;

    cout<<FMT<<"&a.d = "<<&a.d<<endl;

    cout<<FMT<<"&b.d = "<<&b.d<<endl;

    printf("/t&a.fun1 = %p/n",a.fun1);

    printf("/t&a.fun2 = %p/n",a.fun2);

    printf("/t&b.fun1 = %p/n",b.fun1);

    printf("/t&b.fun2 = %p/n",b.fun2);

    cout<<endl;}

// [输出结果]

      sizeof(a) = 12

       &a = 0012FF74

     &a.a = 0012FF74

     &a.b = 0012FF78

     &a.c = 0012FF 7C

       &b = 0012FF68

     &b.a = 0012FF68

     &b.b = 0012FF 6C

     &b.c = 0012FF70

     —————————

     &a.d = 0047873C

     &b.d = 0047873C

     &a.fun1 = 00401235

     &a.fun2 = 00401181

     &b.fun1 = 00401235

     &b.fun2 = 00401181

 

 

 

       对象的大小正好是3non-static变量的大小之和, a,b的地址之后,紧接着的是各自的数据成员的地址:non-static 成员变量分别存在于对象中 ab获得的static数据成员和函数地址(事实上并不是实际的函数地址)都是一样的,因此他们的位置与具体对象无关:成员函数以及static型数据成员则放置于对象之外,对象a,b共享成员函数以及静态成员变量J。可以用下面的图显示:

1

      

       由于数据成员的内存分布连续,那么我们很理所当然的写出下面的代码……

class A

{

    A()

    {

          memset(this,0,sizeof(A));

          // [取代下面的初始化]

        // a = 0;

        // b = 0;

        // c= 0;

};

};

 

 

 

       现在这样写是正确的,不过如果加入虚函数,问题就来了……

具有虚函数对象数据分布

struct A

{

    // [加上virtual]

    virtual void v_fun1(){cout<<"v_fun1"<<endl;};

    virtual void v_fun2(){cout<<"v_fun2"<<endl;};

    int a;

    int b;

    int c;

};

main()

{

    A a,b;

    cout<<FMT<<"sizeof(a) = "<<sizeof(a)<<endl;

    cout<<FMT<<"&a = "<<&a<<endl;

    cout<<FMT<<"&a.a = "<<&a.a<<endl;

    cout<<FMT<<"&a.b = "<<&a.b<<endl;

    cout<<FMT<<"&a.c = "<<&a.c<<endl;

    cout<<endl;

}

// [输出结果]

sizeof(a) = 16

       &a = 0012FF70

     &a.a = 0012FF74

     &a.b = 0012FF78

     &a.c = 0012FF 7C

      

       A的函数加上virtual 之后,再看看结果的不同:

1)        对象a 的大小增大了4字节

2)        对象a的地址和a的第一个成员地址之间有4个字节的不明数据

 不明数据其实就是我们一开始提到过的vptr,编译器私下为对象加入的指针,指向v-table。而v-table中储存着类A的所有虚函数地址 v_fun1, v_fun2

 

 

 

       具体布局如下图所示:

2

 

 

 

为什么要加入这样的机制呢,或者说为什么选择了这样的模型呢?

              主要考虑的还是效率的问题……,For further info,please consult The c++ object model ~J

 

 

 

       再通过代码来验证一下~J~

      

验证思路:

如果存在vptr ,那么他保存的数据就是vitrual-table 的地址,那么,所有的虚函数的真实地址应该都在这里了,知道了地址后,

如何调用呢?

1.         使用正常的成员函数调用方式 (obj.*fun)( parm )

2.         非成员函数的方式调用,既然知道的是真实地址,那么如果再知道函数的原形就解决了,记得this 指针吧,(曾几何时,因为它还莫名其妙了一阵子,哈~),在VC6中,他是做为函数的第一个参数传入的,其他参数以成员函数的一样。

 

 

 

^_^ 就按照这样的思路,把找到的地址直接调用,哈~,希望别找出乱七八糟的地址(大不了程序崩溃)@$%%&(

      

// [获得vptr指针本身的地址就是&a]

int *addr_vptr  = (int*)&a;

// [获得vptr指向的地址(v-table 的首地址)]

int *vptr = (int*)((*addr_vptr));

// [获得第一个函数的地址]

int *p_v_fun = (int*)((*vptr));

 

 

 

// [现在函数地址已经找到了,调用一下看会不会程序崩溃 J~~~!!]

// [记得this指针吧,成员函数经过编译器的捣乱后的函数]

// [——把this做为参数传到成员函数中……]

typedef void(*PF)(A*);

PF pf = (PF)( p_v_fun );

 (*pf)(&a);

 

 

 

// [输出结果]

v_fun1

 

       哇,还真有那么回事,不会是碰巧吧——再来调用一下第二个虚拟函数。

 

 

 

// [virtual table中第2个存储单元地址]

vptr    = (int*)((*addr_vptr)+4);  

// [获得第二个函数的地址] 

p_v_fun = (int*)((*vptr));             

pf      = (PF)( p_v_fun );

(*pf)(&a);

 

 

 

// [输出结果]

v_fun2

 

 

 

       恩,的确是那么回事,HOHO。不信自己试试 ~J~

       继续……

memset 的使用导致错误。

       继续之前,再回顾一下前面分析的最后一段代码。

class A

{

    A()

    {

        memset(this,0,sizeof(A));

        // [取代下面的初始化]

        // a = 0;

        // b = 0;

        // c= 0;

};

};

       现在再看它,应该就能发现问题了!

记得constructor semantic 中说过,编译器初始化vptr的操作会放在constructor user-code 前,~ 类似下面的代码

// [Initialize vptr ……]

……

// [user code]

memset(this,0,sizeof(A));

终于看到狐狸尾巴了J。编译器初始化完vptr后,我们的user-code 又把vptr 给刷了~

编译器白忙了一场……L

 

 

 

继续刚才的话题

 

 

 

       上面虽然有了虚函数,但还没考虑到继承,现在分析一下drive-class中的数据分布,他是如何同base-class 联系起来的……

      

差点忘记今天是国庆节,嘿,休息休息~ 上网看看MM J

      

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值