zoukankan      html  css  js  c++  java
  • 虚表的那些事儿

    话说玩C++的,都肯定知道面向对象的多态性。而要搞清楚多态,又不得不接触虚表(Virtual Table)。

    虚表的结构

    虚表的结构很简单,就是单纯的虚函数指针数组,没有别的条目。

    看一个例子,有如下代码:

    class IUnknown
    {
    public:
        virtual long AddRef() = 0;
        virtual long Release() = 0;
    
    private:
    
    };
    
    class SomeClass : public IUnknown
    {
    public:
        virtual long AddRef() { return ++m_ref; }
        virtual long Release() { return --m_ref; }
    
    private:
        long m_ref;
    };

    代码很简单,然后我们再main函数里面再加上一点code让它跑起来。

    int _tmain(int argc, _TCHAR* argv[])
    {
        SomeClass sc;
        sc.AddRef();
        sc.Release();
    
        return 0;
    }

    我们看一下sc对象的结构,通过windbg

    0:000:x86> dt sc
    Local var @ 0x14fc0c Type SomeClass
       +0x000 __VFN_table : 0x00e2211c 
       +0x004 m_ref            : 14823628

    我们发现sc对象占了8个字节,前4个字节指向的就是虚表了,后面4个字节对应的是变量m_ref,因为m_ref没有初始化,所以这儿是一个随机值14823628,不用管它。

    接着来看虚表里面到底有什么东西:

    0:000:x86> dd 0x00e2211c
    00e2211c  00e21000 00e21030 00e22444 00e21392
    00e2212c  00e21392 00e22408 00e21392 00e21392
    00e2213c  00e21392 00e2241c 00e21392 00e21392
    00e2214c  00e21392 00e223e0 00e21375 00e2136d
    00e2215c  00e21160 00e223f4 00e210e0 00e21110
    00e2216c  00e21140 00000048 00000000 00000000
    00e2217c  00000000 00000000 00000000 00000000
    00e2218c  00000000 00000000 00000000 00000000

    这个东西看的就有点晕了,0x00e2211c这个是前面得到的sc虚表的地址。

    我们把SomeClass的所有符号的地址打印出来看一下:

    0:000:x86> x AboutVirtualTable!SomeClass::*
    00e21000 AboutVirtualTable!SomeClass::AddRef (void)
    00e21320 AboutVirtualTable!SomeClass::SomeClass (void)
    00e21030 AboutVirtualTable!SomeClass::Release (void)
    00e223c0 AboutVirtualTable!SomeClass::`RTTI Class Hierarchy Descriptor' = <no type information>
    00e2229c AboutVirtualTable!SomeClass::`RTTI Base Class Array' = <no type information>
    00e22430 AboutVirtualTable!SomeClass::`RTTI Complete Object Locator' = <no type information>
    00e2211c AboutVirtualTable!SomeClass::`vftable' = <no type information>
    00e22358 AboutVirtualTable!SomeClass::`RTTI Base Class Descriptor at (0,-1,0,64)' = <no type information>

    我们发现00e21000对应的正是AddRef函数的地址,00e21030对应是Release的地址,而00e22444对应的又是个啥玩意儿?

    0:000:x86> dt 00e22444 
    IUnknown::`RTTI Complete Object Locator'

    发现这个是一个表示完成的符号,估计跟标记虚表边界有关。

    其实,虚表的结构如此简洁,以前真是想复杂了。

    C++的多重继承

    一直以来都没有去研究过多重继承的对象的结构,以及虚表的结构。

    来看一个多重继承的例子:

    class IUnknown
    {
    public:
        virtual long AddRef() = 0;
        virtual long Release() = 0;
    
    private:
    
    };
    
    class IMyInterface_1 : public IUnknown
    {
    public:
        virtual void MyMethod_1() = 0;
        long m_i1;
    };
    
    class IMyInterface_2 : public IUnknown
    {
    public:
        virtual void MyMethod_2() = 0;
        long m_i2;
    };
    
    class MyClass : public IMyInterface_1, public IMyInterface_2
    {
        
    public:
        MyClass() : m_ref(0) { m_i1=1; m_i2=2; }
        ~MyClass() { }
        virtual long AddRef() { return ++m_ref; }
        virtual long Release() { return --m_ref; }
        virtual void MyMethod_1() { m_ref = 0; };
        virtual void MyMethod_2() { m_ref = 0; };
    private:
        long m_ref;
    };
    
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    
        MyClass* mc = new MyClass;
    
        IMyInterface_1* i1 = mc;
        IMyInterface_2* i2 = mc;
    
        delete mc;
    
        return 0;
    }
    0:000:x86> dt mc
    Local var @ 0x32f7c8 Type MyClass*
    0x00618e30 
       +0x000 __VFN_table : 0x00102158 
       +0x004 m_i1             : 1
       +0x008 __VFN_table : 0x00102148 
       +0x00c m_i2             : 2
       +0x010 m_ref            : 0

    原来多重继承下,虚表指针并没有放在一起,仔细一想,如果放到一起,还真的没法玩了。

    0:000:x86> dt i1
    Local var @ 0x32f7a8 Type IMyInterface_1*
    0x00618e30 
       +0x000 __VFN_table : 0x00102158 
       +0x004 m_i1             : 1
    0:000:x86> dt i2
    Local var @ 0x32f7a4 Type IMyInterface_2*
    0x00618e38 
       +0x000 __VFN_table : 0x00102148 
       +0x004 m_i2             : 2

    这样i1和i2分别指向各自的虚表。

    再来看看这两个虚表有什么东西。

    0:000:x86> dd 0x00102148 
    00102148  00101285 0010127d 00101100 001023b4
    00102158  00101080 001010b0 001010e0 00000000
    00102168  00000048 00000000 00000000 00000000
    00102178  00000000 00000000 00000000 00000000
    00102188  00000000 00000000 00000000 00000000
    00102198  00000000 00000000 00000000 00103018
    001021a8  00102410 00000002 53445352 043f3dcc
    001021b8  4cb89ef4 784fcf97 ce4e3427 00000007

    00102148对应的是IMyInterface_2的虚表,00102158对应IMyInterface_1的虚表。

    打出符号列表:

    0:000:x86> x AboutVirtualTable!MyClass::*
    001010b0 AboutVirtualTable!MyClass::Release (void)
    00101080 AboutVirtualTable!MyClass::AddRef (void)
    00101060 AboutVirtualTable!MyClass::~MyClass (void)
    00101000 AboutVirtualTable!MyClass::MyClass (void)
    001010e0 AboutVirtualTable!MyClass::MyMethod_1 (void)
    00101100 AboutVirtualTable!MyClass::MyMethod_2 (void)
    00101250 AboutVirtualTable!MyClass::`scalar deleting destructor' (void)
    0010127d AboutVirtualTable!MyClass::Release = <no type information>
    001023a0 AboutVirtualTable!MyClass::`RTTI Complete Object Locator' = <no type information>
    001023b4 AboutVirtualTable!MyClass::`RTTI Complete Object Locator' = <no type information>
    001022f0 AboutVirtualTable!MyClass::`RTTI Base Class Descriptor at (0,-1,0,64)' = <no type information>
    00102148 AboutVirtualTable!MyClass::`vftable' = <no type information>
    00102158 AboutVirtualTable!MyClass::`vftable' = <no type information>
    00101285 AboutVirtualTable!MyClass::AddRef = <no type information>
    00102264 AboutVirtualTable!MyClass::`RTTI Base Class Array' = <no type information>
    00102360 AboutVirtualTable!MyClass::`RTTI Class Hierarchy Descriptor' = <no type information>

    需要注意的是,这里面会有两个AddRef和Release方法,真是很神奇。由于IMyInterface_1和IMyInterface_2都继承自IUnknown。

    同一类型不同对象的虚表是一个吗

    还是上面的代码,稍微修改一下main函数:

    int _tmain(int argc, _TCHAR* argv[])
    {
    
        MyClass* mc = new MyClass;
        MyClass* mc2 = new MyClass;
    
        delete mc;
    
        return 0;
    }
    0:000:x86> dt mc
    Local var @ 0x20fe1c Type MyClass*
    0x00358e30 
       +0x000 __VFN_table : 0x013e2158 
       +0x004 m_i1             : 1
       +0x008 __VFN_table : 0x013e2148 
       +0x00c m_i2             : 2
       +0x010 m_ref            : 0
    0:000:x86> dt mc2
    Local var @ 0x20fe10 Type MyClass*
    0x00358e60 
       +0x000 __VFN_table : 0x013e2158 
       +0x004 m_i1             : 1
       +0x008 __VFN_table : 0x013e2148 
       +0x00c m_i2             : 2
       +0x010 m_ref            : 0
    我们发现这两个对象的虚表指针是一样的,这样显然是合理的。
  • 相关阅读:
    DDOS攻击
    定时器任务:Timer跟ScheduledExecutorService
    11、分布式Dubbo+Zokeeper+SpringBoot
    10、springboot中的任务:异步任务、邮件发送、定时任务
    PHP算法之最长公共前缀
    PHP算法之罗马数字转整数
    PHP算法之整数转罗马数字
    PHP-SQL查询上升的温度
    PHP算法之统计全为 1 的正方形子矩阵
    PHP算法之增减字符串匹配
  • 原文地址:https://www.cnblogs.com/quark/p/3014557.html
Copyright © 2011-2022 走看看