ATL中宏定义offsetofclass的分析

近日学习ATL,通过对宏定义 offsetofclass的解惑过程,顺便分析下虚函数表,以及通过虚函数表调用函数的问题。

解开ATL中宏定义offsetofclass的疑惑
#define  _ATL_PACKING  8  
#define  offsetofclass(base, derived)  (( unsigned  long )( static_cast  <base*>((derived*) _ATL_PACKING ))- _ATL_PACKING )
分析如下:(base 基类 , derived 子类)           
  • (derived*) 8 就是把指针指向地址8,这样就不用自己新创建类的对象。
  • 此后又static_cast <base*>,将指针转为基类指针,这个过程,指针的值实际发生了变化,如果有偏移,那么此时已经指到新的地址,比如12或16(32位系统指针为4字节)
  • 12减去8 就是最后得到的偏移量4
  • 可以看出,_ATL_PACKING 实际上可以是任意非0值,它只是一个地址值,只要不是0,正负均可。
由此得出  offsetofclass 用来计算基类(base)指针在子类( derived )对象中的偏移量,也可以理解为基类虚函数表在子类对象中的偏移量。 因为虚函数表指针就在所有对象的开头位置。 此时大家多有疑问,为什么不通过类对象来计算? 有一个问题,如果 子类是个虚类,它根本就不能创建类对象,所以就没法计算,这个方法解决了虚类的问题,它只是用了下这个地址,并没有修改数据。 (这样任意指向内存地址,不知道有何风险?)

如有两个基类,就有两个虚函数表指针。
class D erived :  public Base1 ,public Base2
offsetofclass(Base1, D erived )   计算出   Base1   D erived 的实例对象中偏移0 字节
offsetofclass(Base2, D erived )   计算出   Base2 D erived 的实例对象中偏移4 字节

通过偏移来指定基类在子类对象中的地址
Derived d;
Derived *pD= &d; //  pD地址0x0018fe98   
Base2 * pB2 = pD; //传递给pB2,地址为0x0018fe9c,偏移了4个字节 
pB2 =     ( Base2 *)((  int )(&d) + 4); // 通过偏移也可以得到Base2 

pB2 和pD地址并不相同 ,而指针判断却相等。
if(pD == pB)
{
     // 两个指针的比较
     //  pD地址 0x0018fe98,pB地址 0x0018fe9c
   // 为什么还是相等呢,pD,pB指向的类型不同,pD先转换成基类 pB 类型,再进行比较。
 }

如下图:
 
          
3 通过虚函数表来调用父类或子类中成员函数
虚函数表,几个基类分支,就有几个虚函数表指针
class D erived: public Base1,public Base2
所以D erived有两个虚函数表指针
如下图:


D erived覆盖了基类的相同虚函数,自己的虚函数放在第一个表中。
清楚了虚函数表,就可以通过地址来调用函数了.

    typedef   void   (*Fun)(  void ); //函数指针
     Derived d;
    int  **pVtab = ( int  **)&d;
    Fun pFun = (Fun)pVtab[0][0];  //等同于 调用 Fun pFun = (Fun)*((int*)*(int*)((int*)&b + 0) + 0);
    pFun();
    以此类推,调用 pVtab[0][1], pVtab[0][2], pVtab[1][0], pVtab[1][1]

输出如下图:


4 如下为全部代码部分
// 测试分3次进行,进行测试1时,请注释掉其他部分,以此类推。

#include  <iostream>
using  namespace  std;

#define  _ATL_PACKING  8  // 尝试修改下 非0 即可
#define  offsetofclass(base, derived) (( unsigned  long )( static_cast  <base*>((derived*) _ATL_PACKING ))- _ATL_PACKING )

typedef   void  (*Fun)(  void );

class Base1
{
public :
                  virtual  void  f() { cout <<  "Base1::f"  << endl; }
                  virtual  void  g() { cout <<  "Base1::g"  << endl; }
};
class Base2
{
public  :
                  virtual   void  f() { cout <<  "Base2::f"  << endl; }
                  virtual   void  g() { cout <<  "Base2::g"  << endl; }
                  // void  h(){ cout << "Base2::h" << endl; }           // 测试2使用: 非虚函数,此函数不在虚表中
};
class D erived :  public Base1 ,public Base2
{
public  :
                  virtual   void  f() { cout <<  " D erived ::f"  << endl; }
                  virtual   void  g1() { cout <<  " D erived ::g1"  << endl; }
                  // virtual void  h() = 0;                                               //测试1使用:子类为虚类时,计算偏移
};

int  main( int  argc,  char * argv[])
{
     //测试1:子类为虚类时,计算偏移
          unsigned  long  nOffset1 =0, nOffset2=0   ;
          nOffset1   = offsetofclass(Base1,Derived);  // 计算后  nOffset1   =0  
          nOffset2  = offsetofclass(Base2,Derived); // 计算后  nOffset2  = 4  
       
     //测试2:创建对象
    unsigned  long  nOffset1 =0, nOffset2=0   ;
          nOffset1   = offsetofclass(Base1,Derived);  // 计算后  nOffset1   =0  
          nOffset2  = offsetofclass(Base2,Derived); // 计算后  nOffset2  = 4  

    Derived d;
    Derived *pD= &d;  //pD地址0x0018fe98
    Base2 * pB2 = pD; // 传递给pB2,地址为0x0018fe9c,偏移了4个字节 
     pB2 =  ( Base2 *)((  int )(&d) + nOffset2 ); // 通过偏移也可以得到Base2    


    // 测试3 通过虚函数表调用 函数
    Derived d;
    Derived *pD= &d;  
     int  **pVtab = ( int  **)&d;
    Fun pFun = (Fun)pVtab[0][0];  //等同于 调用 Fun pFun = (Fun)*((int*)*(int*)((int*)&d+ 0) + 0);
    pFun();
    pFun = (Fun)pVtab[0][1];  //等同于 调用 pFun = (Fun)*((int*)*(int*)((int*)&d + 0 ) + 1);
    pFun();
    pFun = (Fun)pVtab[0][2];  //等同于 调用 pFun = (Fun)*((int*)*(int*)((int*)&d + 0) + 2);
    pFun();
    pFun = (Fun)pVtab[1][0];  //等同于 调用 pFun = (Fun)*((int*)*(int*)((int*)&d + 1) + 0);
    pFun();
    pFun = (Fun)pVtab[1][1];  //等同于 调用 pFun = (Fun)*((int*)*(int*)((int*)&d + 1) + 1);
    pFun();

     


     int  nWait=0;
    cin >> nWait;
}

写本文之前阅读参考了以下文章:
对于这篇文章中提到的 虚函数表在( Windows XP+VS2003)的末尾是个 NULL值,但笔者用(vs2003和vs2013 +win7 debug,release)测试后 末尾并非一定是NULL,值不确定。




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值