zoukankan      html  css  js  c++  java
  • 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 Derived:  public Base1 ,public Base2
    offsetofclass(Base1,Derived)  计算出 Base1 Derived的实例对象中偏移0 字节
    offsetofclass(Base2,Derived)  计算出 Base2Derived的实例对象中偏移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 Derived: public Base1,public Base2
    所以Derived有两个虚函数表指针
    例如以下图:


    Derived覆盖了基类的同样虚函数。自己的虚函数放在第一个表中。
    清楚了虚函数表。就能够通过地址来调用函数了.

        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 Derived:  public Base1 ,public Base2
    {
    public :
                     virtual void f() { cout << "Derived::f" << endl; }
                     virtual void g1() { cout << "Derived::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。值不确定。





  • 相关阅读:
    Ubuntu下Nginx安装
    vi基本状态
    07. 背景图片距离
    06. 用css实现三角形
    Leetcode刷题 (二)
    Leetcode刷题 (一)
    目标检测中的AP计算
    python 引用(import)文件夹下的py文件
    git 上传和克隆文件
    Windows系统下Pytorch与python版本不匹配导致模块包导入错误
  • 原文地址:https://www.cnblogs.com/blfbuaa/p/7043638.html
Copyright © 2011-2022 走看看