zoukankan      html  css  js  c++  java
  • 虚函数表里边保存的不一定是虚函数的地址

    虚函数表里边保存的不一定是虚函数的地址

      我一直以为虚函数表里边保存的就是虚函数的地址,前几天做测试的时候才发现这想法不一定是对的。

    测试代码:

    //虚函数表里边保存的不一定是虚函数的地址.cpp
    //2010.8.19
    /*分析:通过最后的输出结果可以发现,通过Derived类的虚函数表调用所有的虚函数,
    发现第一张虚函数表的输出①和第二张虚函数表的输出④它们是同一个函数的输出,
    在虚函数表项上的值却是不同的。
    如果虚函数表上的项的值都是虚函数的地址,那么Derived的两张表里边用于调用show()函数的表项的值应该是相同的,但事实上它们不同。
    这说明,虚函数表里边保存的未必就是虚函数的地址。
    这种情况在之前一直没有遇到过(或者没注意到),那么那两个不同的值哪一个才是Derived::show()函数的地址呢?
    反汇编分析。。
    //Code::Blocks   VS2005/2008
    */
    #include <iostream>
    using namespace std;
    
    class BaseA
    {
    public:
    	virtual void show()
    	{
    		cout << "BaseA::show()" << endl;
    	}
    	virtual void showAA()
    	{
    	    cout << "BaseA::showAA()" << endl;
    	}
    };
    
    
    class BaseB
    {
    public:
    	virtual void show()
    	{
    		cout << "BaseB::show()" << endl;
    	}
    	virtual void showBB()
    	{
    	    cout << "BaseB::showBB()" << endl;
    	}
    };
    
    class Derived : public BaseA, public BaseB
    {
    public:
        /*重写*/
    	void show()
    	{
    		cout << "Derived::show()" << endl;
    	}
    	virtual void showD()
    	{
    	    cout << "Derived::showD()" << endl;
    	}
    };
    
    
    int main()
    {
    	typedef void (__thiscall *Fun)(void*pThis);//非常重要
    
    	BaseA aobj;
    	BaseB bobj;
    	Derived dobj;
    	/*BaseA对象*/
    	int** p = (int**)&aobj;
    	cout << "-----BaseA类的对象-----" << endl;
    	cout << "①BaseA:\t" << (int*)p[0][0] << "\t"; ((Fun)p[0][0])(p);
    	cout << "②BaseA:\t" << (int*)p[0][1] << "\t"; ((Fun)p[0][1])(p);
    	cout << endl;
    
        /*BaseB对象*/
    	p = (int**)&bobj;
    	cout << "-----BaseB类的对象-----" << endl;
    	cout << "①BaseB:\t" << (int*)p[0][0] << "\t"; ((Fun)p[0][0])(p);
    	cout << "②BaseB:\t" << (int*)p[0][1] << "\t"; ((Fun)p[0][1])(p);
    	cout << endl;
    
        /*Derived对象的第一个虚函数表指针所指向的虚函数表*/
    	p = (int**)&dobj;
    	cout << "-----Derived类的对象-----" << endl;
    	cout << "①Derived:\t" << (int*)p[0][0] << "\t"; ((Fun)p[0][0])(p);
    	cout << "②Derived:\t" << (int*)p[0][1] << "\t"; ((Fun)p[0][1])(p);
    	cout << "③Derived:\t" << (int*)p[0][2] << "\t"; ((Fun)p[0][2])(p);
    	cout << endl;
    
        /*Derived对象的第二个虚函数表指针所指向的虚函数表*/
    	p = (int**)((int*)(&dobj)+1);
    	cout << "④Derived:\t" << (int*)p[0][0] << "\t"; ((Fun)p[0][0])(p);
    	cout << "⑤Derived:\t" << (int*)p[0][1] << "\t"; ((Fun)p[0][1])(p);
    	system("pause");
    	return 0;
    }
    /*
    -----BaseA类的对象-----
    ①BaseA:        00401320        BaseA::show()
    ②BaseA:        00401350        BaseA::showAA()
    
    -----BaseB类的对象-----
    ①BaseB:        004013A0        BaseB::show()
    ②BaseB:        004013D0        BaseB::showBB()
    
    -----Derived类的对象-----
    ①Derived:      00401440        Derived::show()
    ②Derived:      00401350        BaseA::showAA()
    ③Derived:      00401470        Derived::showD()
    
    ④Derived:      00405430        Derived::show()
    ⑤Derived:      004013D0        BaseB::showBB()
    */
    
    

    分析

    一、反汇编分析

      通过测试结果可以发现虚函数表里边保存的可能并非虚函数的地址,但是肯定跟虚函数有一点关联,因为最后通过虚函数表里的表项成功的调用了虚函数。通过反汇编分析,结果表明Derived类的第二张虚函数表里边保存的跟Derived::show()函数相关的表项,并非该函数的地址。分析过程如下:

    1、  第一张虚函数表跟Derived::show()函数相关的表项保存的是该函数的地址。

                

    图 1 通过第一张虚函数表调用show()函数

    CALL EDX 按F7跟进之后见下图:

     

    图 2 Derived::show()函数

    2、  第二张虚函数表跟Derived::show()函数相关的表项保存的不是该函数的地址

     

    图 3通过第二张虚函数表调用show()函数

    CALL EDX 按F7跟进之后见下图:

     

    图 4 跳转

      可以发现,跳转的目标地址正是Derived::show()函数。

    3、 总结:第一张虚函数表里边保存了Derived::show()函数的地址。第二张虚函数表里边保存的不是Derived::show()虚函数的地址,但是跟该虚函数地址间接关联了。

    二、不直接保存函数地址的原因

       现在已经明白,虚函数表里边保存的是什么东西了。还有另外一个问题,为什么第二张虚函数表里边不保存Derived::show()函数的地址,偏偏要保存跳转的地址,然后再跳过去,这样子有什么用途?

       这个跟类的成员函数调用会传递this指针有关。

      假如有这样的语句:

             BaseB* pb = &dobj;

             Pb->show();

       如果没有中间的跳转,直接就去调用show()函数,那么传递的this指针是Derived对象中BaseB类实例的地址,也就是第二张虚函数表地址。这样的话,如果要访问dobj中的成员变量,通过这个this指针访问就会出错。可能Derived::show()会认为传递进来的是Derived对象的BaseA实例的地址。所以就需要图4中的代码,第二张虚函数表的表项保存的是那个sub的地址。在跳转之前先ecx减去4,在例子中可以发现,减去4使得this指针指向了dobj的地址(就是BaseA实例的地址)。

      也就是说,之所以第二张虚函数表里边保存的不是函数地址,是为了保证this指针是正确。

      原因猜测:可以通过A类指针去调用D::show(),也可以通过B类指针去调用D::show(),如果this指针不加调整,D::show()要访问成员变量的时候是this+偏移值来寻址的,这样就会有错误。所以必须调整。

    三、这种情况什么时候出现

          另一个问题, 什么时候虚函数表里边保存的不是函数地址?

      如果要全面测试的话,那实在是件费力的事,所以猜测可能是这种情况:

      派生类D有两个基类A和B,其中A定义了虚函数show(),B也定义了虚函数show(),且D类重写了虚函数show(),这样的D类中的第二张虚函数表(B类实例)里边保存的表项就不是D::show()虚函数地址。

      也就是说通过B类指针调用D类对象的show()函数时,需要调整this指针。

    PS:以上分析以VS2005/2008为编译器,不过,结论对GCC编译器也适应。 

    学习参考:http://topic.csdn.net/u/20091128/14/5a9ff412-560e-4214-8716-e269295f7028.html

    2010.8.20

    cs_wuyg@126.com

    -----------------

    注:本文是在不考虑跳转表的情况下分析的。--2010.9.5

  • 相关阅读:
    iOS中循环引用的解除
    Block的循环引用详解
    Mac OS X下面 Node.js环境的搭建
    swift中闭包和OC的block的对比
    STL priority_queue
    优先使用map(或者unordered_map)的find函数而非algorithm里的find函数
    Insert Interval
    Integer Break
    Unique Binary Search Trees
    腾讯2016实习生笔试
  • 原文地址:https://www.cnblogs.com/cswuyg/p/1804716.html
Copyright © 2011-2022 走看看