精华内容
下载资源
问答
  • 虚函数实现原理

    2017-05-10 10:31:39
    虚函数实现原理

    虚函数实现原理


    http://blog.csdn.net/wanghaobo920/article/details/7674631



    C/C++杂记:虚函数的实现的基本原理

    http://www.cnblogs.com/malecrab/p/5572730.html


    C/C++杂记:深入虚表结构


    http://www.cnblogs.com/malecrab/p/5573368.html


    20170327_请说出虚函数的工作原理

    http://blog.csdn.net/cmm0401/article/details/66971032


    当前标签: C/C++:

    http://www.cnblogs.com/malecrab/tag/C%2FC%2B%2B/

    C/C++杂记:运行时类型识别(RTTI)与动态类型转换原理
    C/C++杂记:深入虚表结构
    C/C++杂记:虚函数的实现的基本原理
    C/C++杂记:深入理解数据成员指针、函数成员指针
    C/C++杂记:NULL与0的区别、nullptr的来历



    展开全文
  • 下面小编就为大家带来一篇浅谈C++中虚函数实现原理揭秘。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • C++虚函数实现原理

    2015-10-24 21:11:09
    虚函数表中虚函数的分布情况;其中包括发生继承的情况下虚函数表中虚函数的分布情况;
  • c++虚函数实现原理

    千次阅读 2018-08-04 09:09:24
    C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。...

    前言

    C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。

    关于虚函数的使用方法,我在这里不做过多的阐述。大家可以看看相关的C++的书籍。在这篇文章中,我只想从虚函数的实现机制上面为大家一个清晰的剖析。

    当然,相同的文章在网上也出现过一些了,但我总感觉这些文章不是很容易阅读,大段大段的代码,没有图片,没有详细的说明,没有比较,没有举一反三。不利于学习和阅读,所以这是我想写下这篇文章的原因。也希望大家多给我提意见。

    言归正传,让我们一起进入虚函数的世界。

    虚函数表

    对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

    这里我们着重看一下这张虚函数表。在C++的标准规格说明书中说到,编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证正确取到虚函数的偏移量)。这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

    听我扯了那么多,我可以感觉出来你现在可能比以前更加晕头转向了。没关系,下面就是实际的例子,相信聪明的你一看就明白了。

    假设我们有这样的一个类:

    class Base {

    public:

    virtual void f() { cout << "Base::f" << endl; }

    virtual void g() { cout << "Base::g" << endl; }

    virtual void h() { cout << "Base::h" << endl; }

     

    };

    按照上面的说法,我们可以通过Base的实例来得到虚函数表。下面是实际例程:

    typedef void(*Fun)(void);

     

    Base b;

     

    Fun pFun = NULL;

     

    cout << "虚函数表地址:" << (int*)(&b) << endl;

    cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) << endl;

     

    // Invoke the first virtual function

    pFun = (Fun)*((int*)*(int*)(&b));

    pFun();

    实际运行经果如下:(Windows XP+VS2003, Linux 2.6.22 + GCC 4.1.3)

    虚函数表地址:0012FED4

    虚函数表 — 第一个函数地址:0044F148

    Base::f

    通过这个示例,我们可以看到,我们可以通过强行把&b转成int *,取得虚函数表的地址,然后,再次取址就可以得到第一个虚函数的地址了,也就是Base::f(),这在上面的程序中得到了验证(把int* 强制转成了函数指针)。通过这个示例,我们就可以知道如果要调用Base::g()和Base::h(),其代码如下:

    (Fun)*((int*)*(int*)(&b)+0); // Base::f()

    (Fun)*((int*)*(int*)(&b)+1); // Base::g()

    (Fun)*((int*)*(int*)(&b)+2); // Base::h()

    这个时候你应该懂了吧。什么?还是有点晕。也是,这样的代码看着太乱了。没问题,让我画个图解释一下。如下所示:

     

     

    注意:在上面这个图中,我在虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“\0”一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。在WinXP+VS2003下,这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,这个值是如果1,表示还有下一个虚函数表,如果值是0,表示是最后一个虚函数表。

    下面,我将分别说明“无覆盖”和“有覆盖”时的虚函数表的样子。没有覆盖父类的虚函数是毫无意义的。我之所以要讲述没有覆盖的情况,主要目的是为了给一个对比。在比较之下,我们可以更加清楚地知道其内部的具体实现。

    一般继承(无虚函数覆盖)

    下面,再让我们来看看继承时的虚函数表是什么样的。假设有如下所示的一个继承关系:

    请注意,在这个继承关系中,子类没有重载任何父类的函数。那么,在派生类的实例中,其虚函数表如下所示:

     

     

    对于实例:Derive d; 的虚函数表如下:

     

    我们可以看到下面几点:

    1)虚函数按照其声明顺序放于表中。

    2)父类的虚函数在子类的虚函数前面。

    我相信聪明的你一定可以参考前面的那个程序,来编写一段程序来验证。

    一般继承(有虚函数覆盖)

    覆盖父类的虚函数是很显然的事情,不然,虚函数就变得毫无意义。下面,我们来看一下,如果子类中有虚函数重载了父类的虚函数,会是一个什么样子?假设,我们有下面这样的一个继承关系。

    为了让大家看到被继承过后的效果,在这个类的设计中,我只覆盖了父类的一个函数:f()。那么,对于派生类的实例,其虚函数表会是下面的一个样子:

    我们从表中可以看到下面几点,

    1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。

    2)没有被覆盖的函数依旧。

    这样,我们就可以看到对于下面这样的程序,

     

    Base *b = new Derive();

     

    b->f();

    由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。

    多重继承(无虚函数覆盖)

    下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类并没有覆盖父类的函数。

    对于子类实例中的虚函数表,是下面这个样子:

     

    我们可以看到:

    1) 每个父类都有自己的虚表。

    2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

    这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

    多重继承(有虚函数覆盖)

    下面我们再来看看,如果发生虚函数覆盖的情况。

    下图中,我们在子类中覆盖了父类的f()函数。

    下面是对于子类实例中的虚函数表的图:

    我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:

     

    Derive d;

    Base1 *b1 = &d;

    Base2 *b2 = &d;

    Base3 *b3 = &d;

    b1->f(); //Derive::f()

    b2->f(); //Derive::f()

    b3->f(); //Derive::f()

    b1->g(); //Base1::g()

    b2->g(); //Base2::g()

    b3->g(); //Base3::g()

    安全性

    每次写C++的文章,总免不了要批判一下C++。这篇文章也不例外。通过上面的讲述,相信我们对虚函数表有一个比较细致的了解了。水可载舟,亦可覆舟。下面,让我们来看看我们可以用虚函数表来干点什么坏事吧。

    一、通过父类型的指针访问子类自己的虚函数

    我们知道,子类没有重载父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数重载的。虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数,但我们根本不可能使用下面的语句来调用子类的自有虚函数:

     

    Base1 *b1 = new Derive();

    b1->f1(); //编译出错

    任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法,所以,这样的程序根本无法编译通过。但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。(关于这方面的尝试,通过阅读后面附录的代码,相信你可以做到这一点)

    二、访问non-public的虚函数

    另外,如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数,这是很容易做到的。

    如:

    class Base {

    private:

    virtual void f() { cout << "Base::f" << endl; }

     

    };

     

    class Derive : public Base{

    };

    typedef void(*Fun)(void);

     

    void main() {

    Derive d;

    Fun pFun = (Fun)*((int*)*(int*)(&d)+0);

    pFun();

    }

     

     

    结束语

    C++这门语言是一门Magic的语言,对于程序员来说,我们似乎永远摸不清楚这门语言背着我们在干了什么。需要熟悉这门语言,我们就必需要了解C++里面的那些东西,需要去了解C++中那些危险的东西。不然,这是一种搬起石头砸自己脚的编程语言。

    在文章束之前还是介绍一下自己吧。我从事软件研发有十个年头了,目前是软件开发技术主管,技术方面,主攻Unix/C/C++,比较喜欢网络上的技术,比如分布式计算,网格计算,P2P,Ajax等一切和互联网相关的东西。管理方面比较擅长于团队建设,技术趋势分析,项目管理。欢迎大家和我交流,我的MSN和Email是:haoel@hotmail.com

    附录一:VC中查看虚函数表

    我们可以在VC的IDE环境中的Debug状态下展开类的实例就可以看到虚函数表了(并不是很完整的)

     

    附录 二:例程

    下面是一个关于多重继承的虚函数表访问的例程:

    #include <iostream>

    using namespace std;

     

    class Base1 {

    public:

    virtual void f() { cout << "Base1::f" << endl; }

    virtual void g() { cout << "Base1::g" << endl; }

    virtual void h() { cout << "Base1::h" << endl; }

     

    };

     

    class Base2 {

    public:

    virtual void f() { cout << "Base2::f" << endl; }

    virtual void g() { cout << "Base2::g" << endl; }

    virtual void h() { cout << "Base2::h" << endl; }

    };

     

    class Base3 {

    public:

    virtual void f() { cout << "Base3::f" << endl; }

    virtual void g() { cout << "Base3::g" << endl; }

    virtual void h() { cout << "Base3::h" << endl; }

    };

     

     

    class Derive : public Base1, public Base2, public Base3 {

    public:

    virtual void f() { cout << "Derive::f" << endl; }

    virtual void g1() { cout << "Derive::g1" << endl; }

    };

     

     

    typedef void(*Fun)(void);

     

    int main()

    {

    Fun pFun = NULL;

     

    Derive d;

    int** pVtab = (int**)&d;

    //Base1's vtable

    //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+0);

    pFun = (Fun)pVtab[0][0];

    pFun();

     

    //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+1);

    pFun = (Fun)pVtab[0][1];

    pFun();

     

    //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+2);

    pFun = (Fun)pVtab[0][2];

    pFun();

     

    //Derive's vtable

    //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+3);

    pFun = (Fun)pVtab[0][3];

    pFun();

     

    //The tail of the vtable

    pFun = (Fun)pVtab[0][4];

    cout<<pFun<<endl;

     

     

    //Base2's vtable

    //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);

    pFun = (Fun)pVtab[1][0];

    pFun();

     

    //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);

    pFun = (Fun)pVtab[1][1];

    pFun();

     

    pFun = (Fun)pVtab[1][2];

    pFun();

     

    //The tail of the vtable

    pFun = (Fun)pVtab[1][3];

    cout<<pFun<<endl;

     

     

     

    //Base3's vtable

    //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);

    pFun = (Fun)pVtab[2][0];

    pFun();

     

    //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);

    pFun = (Fun)pVtab[2][1];

    pFun();

     

    pFun = (Fun)pVtab[2][2];

    pFun();

     

    //The tail of the vtable

    pFun = (Fun)pVtab[2][3];

    cout<<pFun<<endl;

     

    return 0;

    }

    展开全文
  • C++ 虚函数实现原理

    千次阅读 2015-08-05 20:24:39
    首先,我们要明白虚函数的作用:简单讲就是实现多态。  基类定义了虚函数,子类可以重写该函数,当子类重新定义了... 底层实现原理:先来看看C++对象模型    在此模型中,non static 数据成员被放置到对象内部,

        首先,我们要明白虚函数的作用:简单讲就是实现多态

        基类定义了虚函数,子类可以重写该函数,当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态地调用属于子类的该函数,且这样的函数调用是无法在编译期间确认的,而是在运行期确认,也叫做迟绑定。

        底层实现原理:先来看看C++对象模型

       

        在此模型中,non static 数据成员被放置到对象内部,static数据成员, static and nonstatic 函数成员均被放到对象之外。对于虚函数的支持则分两步完成:

        1.每一个class产生一堆指向虚函数的指针,放在表格之中。这个表格称之为虚函数表(virtual table,vtbl)。

        2.每一个对象被添加了一个指针,指向相关的虚函数表vtbl。通常这个指针被称为vptr。vptr的设定(setting)和重置(resetting)都由每一个class的构造函数,析构函数和拷贝赋值运算符自动完成。

        另外,虚函数表地址的前面设置了一个指向type_info的指针,RTTI(Run Time Type Identification)运行时类型识别是有编译器在编译器生成的特殊类型信息,包括对象继承关系,对象本身的描述,RTTI是为多态而生成的信息,所以只有具有虚函数的对象在会生成。

    
    展开全文
  • 从Arm汇编看Android C++虚函数实现原理 Posted on 2016-06-21 | In Android 前言 C++通过虚函数来实现多态,从而在运行时动态决定要调用的函数。那么虚函数的调用过程具体是怎样的呢?本文将基于Arm...

    从Arm汇编看Android C++虚函数实现原理

    前言

    C++通过虚函数来实现多态,从而在运行时动态决定要调用的函数。那么虚函数的调用过程具体是怎样的呢?本文将基于Arm汇编,剖析C++虚函数的调用过程。本文涉及到的代码采用ndk-r10d进行编译。由于水平有限,理解不到位的地方,还请各位指正。

    初窥vtable

    其实虚函数的调用是通过vtable来实现的。编译时,编译器会为每一个申明有虚函数的类生成一个vtable,也就是说vtable和类一一对应。vtable中记录了所有虚函数的地址。在对象初始化时,对象会保存相应vtable的地址,虚函数就可以根据其在vtable中的偏移来进行调用。下面来看一个实际的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    
    class BaseA
    {
    private:
    	int baseA_field;
    public:
    	BaseA(int baseA_field);
    	~BaseA();
    	virtual void baseA_virtual_1()
    	{
    		printf(“\t[-] BaseA::baseA_virtual_1()\n”);
    	}
    	virtual void baseA_virtual_2()
    	{
    		printf(“\t[-] BaseA::baseA_virtual_2()\n”);
    	}
    	virtual void baseA_virtual_3()
    	{
    		printf(“\t[-] BaseA::baseA_virtual_3()\n”);
    	}
    };
    BaseA::BaseA(int baseA_field)
    {
    	printf(“[+] BaseA constructor called\n”);
    	this->baseA_field = baseA_field;
    }
    BaseA::~BaseA()
    {
    	printf(“[+] BaseA destructor called\n\n”);
    }
    
    int main(int argc, char *argv[])
    {
    	BaseA *baseA = new BaseA(1);
    	baseA->baseA_virtual_2();
    	delete baseA;
    	return 0;
    }
    

    上述代码中,类BaseA有一个字段baseA_field和3个虚函数baseA_virtual_1()、baseA_virtual_2()、baseA_virtual_3()。在main()中初始化了一个BaseA的对象,并调用了其虚函数baseA_virtual_2()。

    用IDA分析mian(),F5查看其伪代码,如下图所示:

    第5行申请了8byte的空间,因为BaseA有一个整型字段,再加上vtable的地址所占的空间,共8byte。
    第6行调用sub_5E4(),即BaseA的构造函数,对对象进行初始化。注意调用类的实例方法时,第一个参数始终是对象的地址。BaseA的构造函数伪代码如下:

    由上可以知道,vtable的地址会首先赋给baseA对象的前4个字节,然后才执行构造函数的代码。其中vtable的结构如下:

    执行完构造函数后,BaseA对象的内存如下所示:

    接着看main()伪代码的第7行,*(*v0 + 4)就是虚函数baseA_virtual_2的首地址,因而(*(*v0 + 4))(v0);实际就是调用baseA_virtual_2()。

    无重写虚函数、单继承

    有一个类SubClass继承BaseA,SubClass有一个字段subClass_field和三个虚函数subClass_virtual_1、subClass_virtual_2、subClass_virtual_3,SubClass中没有重写BaseA中的虚函数(ps:不重写虚函数没有多大意义,这里仅仅为了理解虚函数的实现机制),此时代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    
    class SubClass: public BaseA
    {
    private:
    	int subClass_field;
    public:
    	SubClass(int subClass_field, int baseA_field);
    	~SubClass();
    	virtual void subClass_virtula_1()
    	{
    		printf(“\t[-] SubClass::subClass_virtula_1()\n”);
    	}
    	virtual void subClass_virtula_2()
    	{
    		printf(“\t[-] SubClass::subClass_virtula_2()\n”);
    	}
    	virtual void subClass_virtula_3()
    	{
    		printf(“\t[-] SubClass::subClass_virtula_3()\n”);
    	}
    };
    SubClass::SubClass(int subClass_field, int baseA_field) :
    		BaseA(baseA_field)
    {
    	printf(“[+] SubClass constructor called\n”);
    	this->subClass_field = subClass_field;
    }
    SubClass::~SubClass()
    {
    	printf(“[+] SubClass destructor called\n\n”);
    }
    
    int main(int argc, char *argv[])
    {
    	SubClass *subClass = new SubClass(2, 1);
    	subClass->subClass_virtula_3();
    	delete subClass;
    	return 0;
    }
    

    用IDA分析main(),F5查看其伪代码,如下图所示:

    第5行为SubClass对象申请了0xCbyte的内存空间,即vtable地址、baseA_field和subClass_field,各4byte。
    第6行调用SubClass的构造函数,对SubClass对象进行初始化,sub_760()的伪代码如下图所示:

    在SubClass的构造函数中,首先会执行父类BaseA的构造函数,然后将vtable的地址赋给SubClass对象的前4个字节,这样就可以覆盖掉执行BaseA构造函数时赋给SubClass对象BaseA类vtable的地址。其中SubClass类vtable的结构如下图所示:

    执行完构造函数后,SubClass对象的内存如下所示:

    从上可见,SubClass的vtable前面存的是父类BaseA虚函数的地址,后面存的是SubClass中申明的虚函数的地址。
    回头看main()的伪代码,第7行,*(*v0+20)得到vtable第五项的值,即subClass_virtual_3,因而(*(*v0+20))(v0)就是在调用subClass_virtual_3()。

    有重写虚函数、单继承

    现在将SubClass的subClass_ virtual_2()去掉,重写baseA_virtual_2(),代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    class SubClass: public BaseA
    {
    … …
    	virtual void baseA_virtual_2()
    	{
    		printf(“\t[-] SubClass::baseA_virtual_2()\n”);
    	}
    … …
    }
    … …
    int main(int argc, char *argv[])
    {
    	BaseA *subClass = new SubClass(2, 1);
    	subClass->baseA_virtual_2();
    	delete subClass;
    	return 0;
    }
    

    用IDA分析main(),F5查看其伪代码,如下图所示:

    第6行调用SubClass的构造函数进行对象初始化,sub_758()的伪代码如下图所示:

    第9行将vtable的值赋给SubClass对象的前4个字节,vtable的结构如下图所示:

    执行完SubClass的构造函数后,SubClass对象的内存如下所示:

    从上可以知道,无重写时vtable[1] = BaseA::baseA_virtual_2;重写后vtable[1] = SubClass::baseA_virtual_2。因而,在vtable中,子类重写的虚函数会覆盖相应的父类中的虚函数地址。

    回到main()伪代码,第7行中,*(*v0 + 4)获取vtable中第一项的值,由于v0是SubClass对象的指针,因而*(*v0 + 4)就是SubClass::baseA_virtual_2,即(*(*v0 + 4))(v0)调用的是子类SubClass中的baseA_virtual_2()。分析到这里,相信大家对多态的实现机制应该有了一定的认识。

    无重写虚函数、多继承

    C++支持多继承,下面分析多继承情况下虚函数的调用机制。首先分析多继承时,子类没有继承父类虚函数的情况。考虑有如下继承关系的类:

    其中main()的代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    int main(int argc, char *argv[])
    {
    	SubClass *subClass = new SubClass(4, 3, 2, 1);
    	subClass->BaseA::virtual_1();
    	subClass->baseB_virtual_3();
    	subClass->subClass_virtual_1();
    	delete subClass;
    	return 0;
    }
    

    用IDA分析main(),F5查看其伪代码,如下图所示:

    第5行为SubClass对象申请了0x1C byte的空间,其中4个字段占0x10 byte,那么多出的0xC byte存的是什么呢?(其实是3个vtable的指针)
    第6行调用SubClass的构造函数对SubClass对象进行初始化,sub_A1C()的伪代码如下图所示:

    从上可知,由于SubClass类有三个父类,因而SubClass对象中有3个字段分别存着3个指向虚函数列表的地址,subClass类对应的vtable如下图所示:

    执行完SubClass类的构造函数后,SubClass对象的内存结构如下图所示:

    从上可知,SubClass对象的内存结构是由 n * (vtable指针 + 父类字段) + 子类字段(n是父类的数目)构成的,父类是按照申明的顺序排列,SubClass中的虚函数地址存储在第一个父类vtable的后面。

    回到main()的伪代码:

    • 第7行调用sub_5F8(),即调用subClass -> BaseA:: virtual_1(),虽然是调用虚函数,由于采用了作用域,编译器在编译阶段就明确知道要调用的函数,因而这里并没有通过vtable来进行调用。
    • 第8行中,v0[2]是SubClass对象中BaseB类的vtable指针,*(v0[2]+8)得到虚函数BaseB::baseB_virtual_3的地址,因而(*(v0[2] + 8))(v0 + 2);对应源码中的subClass->baseB_virtual_3();。注意这里第0个参数是v0+2,而不是v0,说明调用父类的虚函数时,第0个参数是SubClass对象中该父类所占内存空间的基址(其实调用父类其他函数也是如此)。
    • 通过对第8行的分析,第9行(*(*v0 + 12))(v0);就很好理解了,即调用subClass->subClass_virtual_1();

    有重写虚函数、多继承

    接下来分析多继承时,有重写虚函数的情况。考虑有如下继承关系的类:

    在main()中将SubClass对象指针转为不同的父类指针进行虚函数调用,main()的代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    int main(int argc, char *argv[])
    {
    	SubClass *subClass = new SubClass(4,3,2,1);
    	((BaseA *)subClass)->virtual_1();
    	((BaseB *)subClass)->virtual_2();
    	((BaseC *)subClass)->virtual_1();
    	((BaseC *)subClass)->baseC_virtual_2();
    	delete subClass;
    	return 0;
    }
    

    用IDA分析main(),F5查看其伪代码,如下图所示:

    第6行调用SubClass的构造函数对SubClass对象进行初始化,其构造函数sub_758()的伪代码如下图所示:

    从上可知,编译器对SubClass类的构造函数进行了优化,将对父类构造函数的调用优化成了inline的形式。第16-18行是对vtable指针进行初始化,vtable的结构如下图所示:

    执行完SubClass类的构造函数后,SubClass对象的内存结构如下图所示:

    下面对SubClass对象内存布局进行详细分析(可以与多继承无重写虚函数时的内存结构进行对比):

    • 3个父类都定义了虚函数virtual_1,并且SubClass重写了virtual_1,因而3个父类对应vtable中virtual_1的值都改为了SubClass::virtual_1。这样,当SubClass对象指针转为任意父类的指针调用virtual_1时,调用的都是SubClass::virtual_1;
    • SubClass重写了BaseA和BaseB中的虚函数virtual_2,因而BaseA和BaseB对应vtable中virtual_2的值都改为了SubClass::virtual_2。
    • SubClass还重写了BaseC中的虚函数baseC_virtual_2,因而BaseC对应vtable中baseC_virtual_2的值改为了SubClass::baseC_virtual_2。这样当SubClass对象指针转为BaseC类型的指针调用baseC_virtual_2时,调用的就是SubClass::baseC_vritual_2。注意,由于重写的虚函数baseC_virtual_2不是第一个父类BaseA的虚函数,所以在SubClass类的虚函数列表(位于BaseA虚函数列表的后面)中,还需要按照申明顺序添加SubClass::baseC_virtual_2,这样,SubClass对象的指针就可以调用SubClass::baseC_virtual_2了。

    接着分析main()的伪代码:

    • 第7行,**v0就是SubClass::virtual_1;(**v0)(v0);对应源码中的((BaseA *)subClass)->virtual_1();。
    • 第8行,*(*(v0 + 8) + 4)得到BaseB对应vtable中的SubClass::virtual_2 ,(*(*(v0 + 8) + 4))(v0 + 8);就是((BaseB *)subClass)->virtual_2();。
    • 第9行,**(v0 + 16)得到BaseC对应vtable中的SubClass::virtual_1,(**(v0 + 16))(v0 + 16);就是((BaseC *)subClass)->virtual_1();。
    • 第10行,*(*(v0 + 16) + 4) 得到BaseC对应vtable中的SubClass::baseC_virtual_2,(*(*(v0 + 16) + 4))(v0 + 16);就是((BaseC *)subClass)->baseC_virtual_2();

    至此,分析完了C++虚函数实现的基本原理。对于多重继承的情况,和前面的类似。比如,还有一个类SubSubClass继承SubClass,并重写了虚函数virtual_1,那么将SubClass的vtable中所有的SubClass::virtual_1替换为SubSubClass::virtual_1,得到的结果就是SubSubClass的vtable。

    总结

    本文从Arm汇编的角度分析了Android中C++虚函数的实现机制。编译时,编译器会为每一个申明有虚函数的类生成一个vtable,当对象初始化时,会将vtable的地址赋给对象,这样虚函数就可以根据其在vtable中的偏移来进行调用。其中,一个对象所占的内存空间不仅与类的字段有关系,还与类的继承关系有关,对于单继承,一个对象会有一个vtable的指针;如果继承关系中存在多继承,那么该对象会为每一个多继承的父类保存一个vtable的指针。

    参考

    展开全文
  • 简单地说,每一个含有虚函数(无论是其本身的,还是继承而来的)的类都至少有一个与之对应的虚函数表,其中存放着该类所有的虚函数对应的函数指针。例: 其中: B的虚函数表中存放着B::foo和B::bar两个函数指针。 ...
  • C++虚函数实现原理与代价

    千次阅读 2015-11-20 10:47:16
    一 引言 C++面向对象语言的一大特性就是抽象,在程序设计上的体现就是鼓励面向接口编程,而不要面向具体实现编程...虚函数是C++语言一个非常重要的特性,不同编译器对此特性的实现机制也略有差别,虽然具体实现细节
  • C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。...
  • 虚函数的定义要遵循以下重要规则: 1.如果虚函数在基类与派生类中出现,仅仅是名字相同,而形式参数不同,或者是返回类型不同,那么即使加上了virtual关键字,也是不会进行滞后联编的。  2.只有类的成员函数才能...
  • C++常见面试题:虚函数实现原理

    千次阅读 2017-08-15 17:04:52
    转载地址:http://blog.csdn.NET/wanghaobo920/article/details/7674631前言C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类...
  • 【C++拾遗】 C++虚函数实现原理

    千次阅读 多人点赞 2016-01-04 15:47:39
    原创作品,转载请标明:http://blog.csdn.net/Xiejingfa/article/details/50454819我们知道,与C语言相比,C++在...在前面一篇文章中,我们从内存布局的角度入手,分析了继承的实现原理,传送门:从内存布局看C++
  • 虚函数实现原理(转)

    万次阅读 多人点赞 2012-06-18 21:06:49
    C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。...
  • C++对象模型之虚函数实现原理

    千次阅读 2013-05-11 14:30:08
    现在刚好有时间,就写一下自己对C++在单一继承情况下如何实现虚函数的肤浅认识。 一旦一个类有一个虚函数,那么一定会建立一个虚表(virtual table),虚表里面有所有虚函数的地址。虚表是该类所有
  •  虚函数的作用是实现动态联编,也就是在程序的运行阶段动态地选择合适的成员函数,在定义了虚函数后,可以在基类的派生类中对虚函数重新定义,在派生类中重新定义的函数应与虚函数具有相同的形参个数和形参类型。...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 49,028
精华内容 19,611
关键字:

虚函数实现原理