C++虚函数表的两个知识点

  • 虚函数表并不依赖对象而存在

    虚函数表是在编译期就确定的,意味着在可执行文件或动态库文件中是固定存在的,

    如果打印虚函数表的地址,可以发现每次都是一致的(需关闭ALSR功能,参https://www.cnblogs.com/csnd/p/11800531.html

    这是 “虚函数表并不依赖对象而存在” 成立的原因,

    测试代码如下:

    class A
    {
    public:
        virtual void func(){puts("this is virtual func");}
    };
    typedef void (*pfunc_t)();
    int main()
    {
        A * p = new A;
        pfunc_t p2 = (pfunc_t)(*(int*)(*(int*)p));
        delete p;
        p2();
        return 0;
     }

     测试结果:

     可以正常调用调用func函数,而这之前p对象已经释放了(注意:如果函数中访问了类成员变量,就不能这么调了)
     对main中第二句的解释: 
     (int *)p 是将p解释为指向int(4字节)的指针,
     *(int*)p 取到的是p指向的对象的前4字节的内容(指向需表的地址)
     (int*)(*(int*)p) 把虚表的地址解释为指向int的指针
     *(int*)(*(int*)p) 取出虚表的前4个字节的内容(指向第一个虚函数)

  • 避免在构造/析构函数中调用虚函数,因为这通常并不能达到你所需要的效果

    构造函数中,因为基类构造函数先被调用,
    基类构造函数执行期间,会把前4个字节首先指向基类的虚函数表,
    等到派生类构造函数调用时,才会把前4个字节更改为派生类的虚函数表,
    所以在基类构造函数中调用的虚函数,不会是派生类的虚函数,而是自己的虚函数
    析构函数链的调用顺序则是派生类析构函数先被调用,然后是基类析构函数
    派生类析构函数调用完成之后,基类析构函数会把对象的前4个字节重新指向基类的虚函数表
    所以在基类中调用的虚函数,也将会使基类自己的虚函数,而不是派生类中的虚函数。
    试验:

 class A
    {
    public:
        A()
        {
            printf("this = %p\n",this);
            printf("this 前4字节的值为 %d\n",*(int*)this);
            pfunc_t p2 = (pfunc_t)(*(int*)(*(int*)this)); 
            p2();
        }
        ~A()
        {
            printf("this = %p\n",this);
            printf("this 前4字节的值为 %d\n",*(int*)this);
            pfunc_t p2 = (pfunc_t)(*(int*)(*(int*)this)); 
            p2();
        }
        virtual void func()
        {
            puts("this is v func in A");
        }
    };
    class B : public A
    {
    public:
        B(){
            printf("this = %p\n",this);
            printf("this 前4字节的值为 %d\n",*(int*)this);
            pfunc_t p2 = (pfunc_t)(*(int*)(*(int*)this)); 
            p2();
        }
        ~B(){
            printf("this = %p\n",this);
            printf("this 前4字节的值为 %d\n",*(int*)this);
            pfunc_t p2 = (pfunc_t)(*(int*)(*(int*)this)); 
            p2();
        }
        virtual void func()
        {
            puts("this is v func in B");
        }
    };
    int main()
    {
        B * p = new B;
        delete p;
        return 0;
    }

    执行结果
    this = 00462320
    this 前4字节的值为 3772236    //这个值是基类虚函数表的位置
    this is v func in A

    this = 00462320
    this 前4字节的值为 3771628    //这个值是派生类虚函数表的位置
    this is v func in B

    this = 00462320
    this 前4字节的值为 3771628    //这个值是派生类虚函数表的位置
    this is v func in B

    this = 00462320
    this 前4字节的值为 3772236    //这个值是基类虚函数表的位置
    this is v func in A

    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值