精华内容
下载资源
问答
  • 在了解虚函数之前先了解下对象模型:对象模型: 在C++中,有两种数据成员(class data members):static 和nonstatic,以及三种类成员函数(class member functions):static、nonstatic和virtual: 说明:采用的是非继承...

    在了解虚函数之前先了解下对象模型:

    对象模型: 在C++中,有两种数据成员(class data members):static 和nonstatic,以及三种类成员函数(class member functions):static、nonstatic和virtual:

    cfe14defd25084268400880be2bdbe15.png

    说明:采用的是非继承下的C++对象模型:

    nonstatic 数据成员被置于每一个类对象中,而static数据成员被置于类对象之外。static与nonstatic函数也都放在类对象之外,而对于virtual 函数,则通过虚函数表+虚指针来支持,具体如下:

    每个类生成一个表格,称为虚表(virtual table,简称vtbl)。虚表中存放着一堆指针,这些指针指向该类每一个虚函数。虚表中的函数地址将按声明时的顺序排列,不过当子类有多个重载函数时例外。

    每个类对象都拥有一个虚表指针(vptr),由编译器为其生成。虚表指针的设定与重置皆由类的复制控制(也即是构造函数、析构函数、赋值操作符)来完成。vptr的位置为编译器决定,传统上它被放在所有显示声明的成员之后,不过现在许多编译器把vptr放在一个类对象的最前端。

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

    81c072511a9b723e0b396376c3e90f08.png

    c25b0ed935113746c722579cfaeebcb0.png

    圆圈代表类中可以供每个对象共享的部分,矩形表示每个对象自己含有的部分。

    简介

    虚函数(virtual)是为了c++实现多态而采用的方法,并使用了动态绑定的技术-虚函数表。每一个包含虚函数的类都会有一个虚表,若基类含有虚函数,则派生类也要有自己的虚表。 通过使用这些虚函数表,即使使用的是基类的指针来调用函数,也可以达到正确调用运行中实际对象的虚函数。

    例如: 类A包含虚函数vfunc1,vfunc2,由于类A包含虚函数,故类A拥有一个虚表。

    class A {

    public:

    virtual void vfunc1();

    virtual void vfunc2();

    void func1();

    void func2();

    private:

    int m_data1, m_data2;

    };

    则类A的虚表(虚表只包含虚函数)如图:

    77c6e5ca5eef4c930631a3016cd432a4.png

    虚表其实是一个指针数组,每个元素是指向虚函数的指针,普通函数为非虚函数,不需要通过虚表,虚表内的条目即虚函数指针的赋值发生在编译器的编辑阶段,在代码编译时,就已经构造出来了。

    2.虚表指针

    虚表属于类所有,不属于某个具体的对象。一个类只需要有一个虚表即可。同一个类的对象使用同一个虚表。为了指定对象的虚表,对象内部包含一个指向虚表的指针,来指向自己所使用的虚表。 为了让每个包含虚表的类的对象都拥有一个虚表指针,编译器在类中添加了一个指针,*__vptr,用来指向虚表 当类的对象在创建是便有了这个指针,且指针的值会自动被设置为指向类的虚表。

    c7479e114454011fba9c63007dbe1a88.png

    说明:一个继承类的基类如果包含虚函数,那个这个继承类也有拥有自己的虚表,故这个继承类的对象也包含一个虚表指针,用来指向它的虚表。

    4.动态绑定

    class A {

    public:

    virtual void vfunc1();

    virtual void vfunc2();

    void func1();

    void func2();

    private:

    int m_data1, m_data2;

    };

    class B : public A {

    public:

    virtual void vfunc1();

    void func1();

    private:

    int m_data3;

    };

    class C: public B {

    public:

    virtual void vfunc2();

    void func2();

    private:

    int m_data1, m_data4;

    };

    对象模型如图:

    aadb4a429714b6b3c9b56a82c49e7821.png

    说明:A是基类,B继承了A,C又继承了B。三个类中都有虚函数,故编译器为每个类都会创建一个虚表。每个类中的每个对象都有一个虚表指针,指向自己所属类的虚表。

    类A包括两个虚函数,故A vtbl包含两个指针,分别指向A::vfunc1()和A::vfunc2()。

    类B继承于类A,故类B可以调用类A的函数,但由于类B重写了B::vfunc1()函数,故B vtbl的两个指针分别指向B::vfunc1()和A::vfunc2()。

    类C继承于类B,故类C可以调用类B的函数,但由于类C重写了C::vfunc2()函数,故C vtbl的两个指针分别指向B::vfunc1()(指向继承的最近的一个类的函数)和C::vfunc2()。

    法则:对象的虚表指针用来指向自己所属类的虚表,虚表中的指针会指向器继承的最近一个类的虚函数

    5.父类通过虚表指针访问子类的虚函数

    int main()

    {

    B bObject;

    A *p = & bObject;

    }

    说明:bObject是类B的一个对象,故bObject包含一个虚表指针。声明一个类A的指针指向对象bObject。

    以上代码执行步骤:

    根据虚表指针p->__vptr来访问对象bObject对应的虚表。虽然指针p是基类A*类型,但是*__vptr也是基类的一部分,所以可以通过p->__vptr可以访问到对象对应的虚表。

    在虚表中查找所调用的函数对应的条目。由于虚表在编译阶段就可以构造出来了,所以可以根据所调用的函数定位到虚表中的对应条目。对于 p->vfunc1()的调用,B vtbl的第一项即是vfunc1对应的条目。

    根据虚表中找到的函数指针,调用函数。从图3可以看到,B vtbl的第一项指向B::vfunc1(),所以 p->vfunc1()实质会调用B::vfunc1()函数。

    97b67cbef9bc396d6e52492b504889e0.png  《-------》    指针p是基类A*类型,但是*__vptr也是基类的一部分的理解。

    6.静态绑定和动态绑定,运行时多态,编译时多态

    把经过虚表调用虚函数的过程称为动态绑定,其表现出来的现象称为运行时多态。动态绑定区别于传统的函数调用,传统的函数调用我们称之为静态绑定,即函数的调用在编译阶段就可以确定下来了。 动态绑定的三个条件:

    通过指针来调用函数

    指针upcast向上转型(继承类向基类的转换称为upcast,关于什么是upcast,可以参考本文的参考资料)

    调用的是虚函数

    符合三个条件,编译器就会把该函数调用编译为动态绑定,调用时走虚表的机制。

    展开全文
  • C++ 虚函数表 vfptr

    2021-01-21 16:37:30
    虚函数带来的好处就是:可以定义一个基类的指针,其指向一个继承类,当通过基类的指针去调用函数时,可以在运行时决定该调用基类的函数还是继承类的函数()。虚函数是实现多态(动态绑定)/接口函数的基础. 可以说: ...

    前言

    大家都应该知道C++的精髓是虚函数吧? 虚函数带来的好处就是:可以定义一个基类的指针,其指向一个继承类,当通过基类的指针去调用函数时,可以在运行时决定该调用基类的函数还是继承类的函数(运行时指向谁即调用谁的函数)。虚函数是实现多态(动态绑定)/接口函数的基础。 可以说:没有虚函数,C++将变得一无是处!

    既然是C++的精髓, 那么我们有必要了解一下她的实现方式吗? 有必要! 既然C++是从C语言的基础上发展而来的, 那么我们可以尝试用C语言来模拟实现吗? 有可能! 接下来, 就是我一步一步地来解析C++的虚函数的实现方式, 以及用C语言对其进行的模拟.


    C++对象的内存布局

    要想知道C++对象的内存布局, 可以有多种方式, 比如:

    1. 输出成员变量的偏移, 通过offsetof宏来得到
    2. 通过调试器查看, 比如常用的VS

    只有数据成员的对象

    类实现如下:

    class Base1
    {
    public:
        int base1_1;
        int base1_2;
    };

    对象大小及偏移:

    sizeof(Base1);               8
    offsetof(Base1, base1_1);    0
    offsetof(Base1, base1_2);    4
    

    可知对象布局:
    这里写图片描述

    可以看到, 成员变量是按照定义的顺序来保存的, 最先声明的在最上边, 然后依次保存!
    类对象的大小就是所有成员变量大小之和.


    没有虚函数的对象

    class Base1
    {
    public:
        int base1_1;
        int base1_2;
    
        void foo(){}
    };

    结果如下:

    sizeof(Base1)               8
    offsetof(Base1, base1_1)    0
    offsetof(Base1, base1_2)    4
    

    和前面的结果是一样的? 不需要有什么疑问对吧?
    因为如果一个函数不是虚函数,那么他就不可能会发生动态绑定,也就不会对对象的布局造成任何影响.
    当调用一个非虚函数时, 那么调用的一定就是当前指针类型拥有的那个成员函数. 这种调用机制在编译时期就确定下来了.


    拥有仅一个虚函数的类对象

    类实现如下:

    class Base1
    {
    public:
        int base1_1;
        int base1_2;
    
        virtual void base1_fun1() {}
    };

    结果如下:

    sizeof(Base1)               12
    offsetof(Base1, base1_1)    4
    offsetof(Base1, base1_2)    8

    咦? 多了4个字节? (64位机器上虚函数表指针占8个字节)且 base1_1 和 base1_2 的偏移都各自向后多了4个字节!
    说明类对象的最前面被多加了4个字节的”东东”, what’s it?
    现在, 我们通过VS2013来瞧瞧类Base1的变量b1的内存布局情况:
    (由于我没有写构造函数, 所以变量的数据没有根据, 但虚函数是编译器为我们构造的, 数据正确!)
    (Debug模式下, 未初始化的变量值为0xCCCCCCCC, 即:-858983460)

    这里写图片描述

    看到没? base1_1前面多了一个变量 __vfptr(常说的虚函数表vtable指针), 其类型为void**, 这说明它是一个void*指针(注意:不是数组).

    再看看[0]元素, 其类型为void*, 其值为 ConsoleApplication2.exe!Base1::base1_fun1(void), 这是什么意思呢? 如果对WinDbg比较熟悉, 那么应该知道这是一种惯用表示手法, 她就是指 Base1::base1_fun1() 函数的地址.

    可得, __vfptr的定义伪代码大概如下:

    void*   __fun[1] = { &Base1::base1_fun1 };
    const void**  __vfptr = &__fun[0];

    值得注意的是:

    1. 上面只是一种伪代码方式, 语法不一定能通过
    2. 该类的对象大小为12个字节, 大小及偏移信息如下:
    3. 大家有没有留意这个__vfptr? 为什么它被定义成一个指向指针数组的指针, 而不是直接定义成一个指针数组呢?
      我为什么要提这样一个问题? 因为如果仅是一个指针的情况, 您就无法轻易地修改那个数组里面的内容, 因为她并不属于类对象的一部分.属于类对象的, 仅是一个指向虚函数表的一个指针__vfptr而已, 下一节我们将继续讨论这个问题.
    4. 注意到__vfptr前面的const修饰. 她修饰的是那个虚函数表, 而不是__vfptr.

    现在的对象布局如下:
    这里写图片描述

    虚函数指针__vfptr位于所有的成员变量之前定义.

    注意到: 我并未在此说明__vfptr的具体指向, 只是说明了现在类对象的布局情况.
    接下来看一个稍微复杂一点的情况, 我将清楚地描述虚函数表的构成.


    拥有多个虚函数的类对象

    和前面一个例子差不多, 只是再加了一个虚函数. 定义如下:

    class Base1
    {
    public:
        int base1_1;
        int base1_2;
    
        virtual void base1_fun1() {}
        virtual void base1_fun2() {}
    };

    大小以及偏移信息如下:
    这里写图片描述

    有情况!? 多了一个虚函数, 类对象大小却依然是12个字节!

    再来看看VS形象的表现:

    呀, __vfptr所指向的函数指针数组中出现了第2个元素, 其值为Base1类的第2个虚函数base1_fun2()的函数地址.

    现在, 虚函数指针以及虚函数表的伪定义大概如下:

    void* __fun[] = { &Base1::base1_fun1, &Base1::base1_fun2 };
    const void** __vfptr = &__fun[0];

    通过上面两张图表, 我们可以得到如下结论:

    1. 更加肯定前面我们所描述的: __vfptr只是一个指针, 她指向一个函数指针数组(即: 虚函数表)
    2. 增加一个虚函数, 只是简单地向该类对应的虚函数表中增加一项而已, 并不会影响到类对象的大小以及布局情况

    前面已经提到过: __vfptr只是一个指针, 她指向一个数组, 并且: 这个数组没有包含到类定义内部, 那么她们之间是怎样一个关系呢?

    不妨, 我们再定义一个类的变量b2, 现在再来看看__vfptr的指向:

    通过Watch 1窗口我们看到:

    b1和b2是类的两个变量, 理所当然, 她们的地址是不同的(见 &b1 和 &b2)
    虽然b1和b2是类的两个变量, 但是: 她们的__vfptr的指向却是同一个虚函数表
    由此我们可以总结出:

    同一个类的不同实例共用同一份虚函数表, 她们都通过一个所谓的虚函数表指针__vfptr(定义为void**类型)指向该虚函数表.

    是时候该展示一下类对象的内存布局情况了:

    不出意外, 很清晰明了地展示出来了吧? :-) hoho~~

    那么问题就来了! 这个虚函数表保存在哪里呢? 其实, 我们无需过分追究她位于哪里, 重点是:

    1. 她是编译器在编译时期为我们创建好的, 只存在一份
    2. 定义类对象时, 编译器自动将类对象的__vfptr指向这个虚函数表

    单继承且本身不存在虚函数的继承类的内存布局

    前面研究了那么多啦, 终于该到研究继承类了! 先研究单继承!

    依然, 简单地定义一个继承类, 如下:

    class Base1
    {
    public:
        int base1_1;
        int base1_2;
    
        virtual void base1_fun1() {}
        virtual void base1_fun2() {}
    };
    
    class Derive1 : public Base1
    {
    public:
        int derive1_1;
        int derive1_2;
    };

    我们再来看看现在的内存布局(定义为Derive1 d1):

    没错! 基类在上边, 继承类的成员在下边依次定义! 展开来看看:

    经展开后来看, 前面部分完全就是Base1的东西: 虚函数表指针+成员变量定义.
    并且, Base1的虚函数表的[0][1]两项还是其本身就拥有的函数: base1_fun1() 和 base1_fun2().


    本身不存在虚函数(不严谨)但存在基类虚函数覆盖的单继承类的内存布局

    标题本身不存在虚函数的说法有些不严谨, 我的意思是说: 除经过继承而得来的基类虚函数以外, 自身没有再定义其它的虚函数.

    Ok, 既然存在基类虚函数覆盖, 那么来看看接下来的代码会产生何种影响:

    class Base1
    {
    public:
        int base1_1;
        int base1_2;
    
        virtual void base1_fun1() {}
        virtual void base1_fun2() {}
    };
    
    class Derive1 : public Base1
    {
    public:
        int derive1_1;
        int derive1_2;
    
        // 覆盖基类函数
        virtual void base1_fun1() {}
    };

    可以看到, Derive1类 重写了Base1类的base1_fun1()函数, 也就是常说的虚函数覆盖. 现在是怎样布局的呢?

    特别注意我高亮的那一行: 原本是Base1::base1_fun1(), 但由于继承类重写了基类Base1的此方法, 所以现在变成了Derive1::base1_fun1()!

    那么, 无论是通过Derive1的指针还是Base1的指针来调用此方法, 调用的都将是被继承类重写后的那个方法(函数), 多态发生了!!!

    那么新的布局图:
    这里写图片描述


    定义了基类没有的虚函数的单继承的类对象布局

    class Base1
    {
    public:
        int base1_1;
        int base1_2;
    
        virtual void base1_fun1() {}
        virtual void base1_fun2() {}
    };
    
    class Derive1 : public Base1
    {
    public:
        int derive1_1;
        int derive1_2;
    
        virtual void derive1_fun1() {}
    };

    和第5类不同的是多了一个自身定义的虚函数. 和第6类不同的是没有基类虚函数的覆盖.

    咦, 有没有发现问题? 表面上看来几乎和第5种情况完全一样? 为嘛呢?
    现在继承类明明定义了自身的虚函数, 但不见了??
    那么, 来看看类对象的大小, 以及成员偏移情况吧:

    居然没有变化!!! 前面12个字节是Base1的, 有没有觉得很奇怪?

    好吧, 既然表面上没办法了, 我们就只能从汇编入手了, 来看看调用derive1_fun1()时的代码:

    Derive1 d1;
    Derive1* pd1 = &d1;
    pd1->derive1_fun1();

    要注意: 我为什么使用指针的方式调用? 说明一下: 因为如果不使用指针调用, 虚函数调用是不会发生动态绑定的哦! 你若直接 d1.derive1_fun1(); , 是不可能会发生动态绑定的, 但如果使用指针: pd1->derive1_fun1(); , 那么 pd1就无从知道她所指向的对象到底是Derive1 还是继承于Derive1的对象, 虽然这里我们并没有对象继承于Derive1, 但是她不得不这样做, 毕竟继承类不管你如何继承, 都不会影响到基类, 对吧?

    ; pd1->derive1_fun1();
    00825466  mov         eax,dword ptr [pd1]  
    00825469  mov         edx,dword ptr [eax]  
    0082546B  mov         esi,esp  
    0082546D  mov         ecx,dword ptr [pd1]  
    00825470  mov         eax,dword ptr [edx+8]  
    00825473  call        eax

    汇编代码解释:

    第2行: 由于pd1是指向d1的指针, 所以执行此句后 eax 就是d1的地址
    第3行: 又因为Base1::__vfptr是Base1的第1个成员, 同时也是Derive1的第1个成员, 那么: &__vfptr == &d1, clear? 所以当执行完 mov edx, dword ptr[eax] 后, edx就得到了__vfptr的值, 也就是虚函数表的地址.
    第5行: 由于是__thiscall调用, 所以把this保存到ecx中.
    第6行: 一定要注意到那个 edx+8, 由于edx是虚函数表的地址, 那么 edx+8将是虚函数表的第3个元素, 也就是__vftable[2]!!!
    第7行: 调用虚函数.

    结果:

    1. 现在我们应该知道内幕了! 继承类Derive1的虚函数表被加在基类的后面! 事实的确就是这样!
    2. 由于Base1只知道自己的两个虚函数索引[0][1], 所以就算在后面加上了[2], Base1根本不知情, 不会对她造成任何影响.
    3. 如果基类没有虚函数呢? 这个问题我们留到第9小节再来讨论!

    最新的类对象布局表示:


    多继承且存在虚函数覆盖同时又存在自身定义的虚函数的类对象布局

    真快, 该看看多继承了, 多继承很常见, 特别是接口类中!

    依然写点小类玩玩:

    class Base1
    {
    public:
        int base1_1;
        int base1_2;
    
        virtual void base1_fun1() {}
        virtual void base1_fun2() {}
    };
    
    class Base2
    {
    public:
        int base2_1;
        int base2_2;
    
        virtual void base2_fun1() {}
        virtual void base2_fun2() {}
    };
    
    // 多继承
    class Derive1 : public Base1, public Base2
    {
    public:
        int derive1_1;
        int derive1_2;
    
        // 基类虚函数覆盖
        virtual void base1_fun1() {}
        virtual void base2_fun2() {}
    
        // 自身定义的虚函数
        virtual void derive1_fun1() {}
        virtual void derive1_fun2() {}
    };

    代码变得越来越长啦! 为了代码结构清晰, 我尽量简化定义.

    初步了解一下对象大小及偏移信息:

    貌似, 若有所思? 不管, 来看看VS再想:

    哇, 不摆了! 一丝不挂啊! :-)

    结论:

    1. 按照基类的声明顺序, 基类的成员依次分布在继承中.
    2. 注意被我高亮的那两行, 已经发生了虚函数覆盖
    3. 我们自己定义的虚函数呢? 怎么还是看不见?!

    好吧, 继承反汇编, 这次的调用代码如下:

    Derive1 d1;
    Derive1* pd1 = &d1;
    pd1->derive1_fun2();

    反汇编代码如下:

    ; pd1->derive1_fun2();
    00995306  mov         eax,dword ptr [pd1]  
    00995309  mov         edx,dword ptr [eax]  
    0099530B  mov         esi,esp  
    0099530D  mov         ecx,dword ptr [pd1]  
    00995310  mov         eax,dword ptr [edx+0Ch]  
    00995313  call        eax

    解释下, 其实差不多:

    第2行: 取d1的地址
    第3行: 取Base1::__vfptr的值!!
    第6行: 0x0C, 也就是第4个元素(下标为[3])

    结论:

    Derive1的虚函数表依然是保存到第1个拥有虚函数表的那个基类的后面的.

    看看现在的类对象布局图:

    如果第1个基类没有虚函数表呢? 进入第9节!

    9.如果第1个直接基类没有虚函数(表)

    class Base1
    {
    public:
        int base1_1;
        int base1_2;
    };
    
    class Base2
    {
    public:
        int base2_1;
        int base2_2;
    
        virtual void base2_fun1() {}
        virtual void base2_fun2() {}
    };
    
    // 多继承
    class Derive1 : public Base1, public Base2
    {
    public:
        int derive1_1;
        int derive1_2;
    
        // 自身定义的虚函数
        virtual void derive1_fun1() {}
        virtual void derive1_fun2() {}
    };

    来看看VS的布局:

    这次相对前面一次的图来说还要简单啦! Base1已经没有虚函数表了! (真实情况并非完全这样, 请继续往下看!)

    现在的大小及偏移情况: 注意: sizeof(Base1) == 8;

    重点是看虚函数的位置, 进入函数调用(和前一次是一样的):

    Derive1 d1;
    Derive1* pd1 = &d1;
    pd1->derive1_fun2();

    反汇编调用代码:

    ; pd1->derive1_fun2();
    012E4BA6  mov         eax,dword ptr [pd1]  
    012E4BA9  mov         edx,dword ptr [eax]  
    012E4BAB  mov         esi,esp  
    012E4BAD  mov         ecx,dword ptr [pd1]  
    012E4BB0  mov         eax,dword ptr [edx+0Ch]  
    012E4BB3  call        eax

    这段汇编代码和前面一个完全一样!, 那么问题就来了! Base1 已经没有虚函数表了, 为什么还是把b1的第1个元素当作__vfptr呢?
    不难猜测: 当前的布局已经发生了变化, 有虚函数表的基类放在对象内存前面!? , 不过事实是否属实? 需要仔细斟酌.

    我们可以通过对基类成员变量求偏移来观察:

    可以看到:

    &d1==0x~d4
    &d1.Base1::__vfptr==0x~d4
    &d1.base2_1==0x~d8
    &d1.base2_2==0x~dc
    &d1.base1_1==0x~e0
    &d1.base1_2==0x~e4

    所以不难验证: 我们前面的推断是正确的, 谁有虚函数表, 谁就放在前面!

    现在类的布局情况:

    那么, 如果两个基类都没有虚函数表呢?

    10.What if 两个基类都没有虚函数表

    class Base1
    {
    public:
        int base1_1;
        int base1_2;
    };
    
    class Base2
    {
    public:
        int base2_1;
        int base2_2;
    };
    
    // 多继承
    class Derive1 : public Base1, public Base2
    {
    public:
        int derive1_1;
        int derive1_2;
    
        // 自身定义的虚函数
        virtual void derive1_fun1() {}
        virtual void derive1_fun2() {}
    };

    前面吃了个亏, 现在先来看看VS的基本布局:

    可以看到, 现在__vfptr已经独立出来了, 不再属于Base1和Base2!

    看看求偏移情况:

    Ok, 问题解决! 注意高亮的那两行, &d1==&d1.__vfptr, 说明虚函数始终在最前面!

    不用再废话, 相信大家对这种情况已经有底了.

    11.如果有三个基类: 虚函数表分别是有, 没有, 有!

    这种情况其实已经无需再讨论了, 作为一个完结篇….

    上代码:

    class Base1
    {
    public:
        int base1_1;
        int base1_2;
    
        virtual void base1_fun1() {}
        virtual void base1_fun2() {}
    };
    
    class Base2
    {
    public:
        int base2_1;
        int base2_2;
    };
    
    class Base3
    {
    public:
        int base3_1;
        int base3_2;
    
        virtual void base3_fun1() {}
        virtual void base3_fun2() {}
    };
    
    // 多继承
    class Derive1 : public Base1, public Base2, public Base3
    {
    public:
        int derive1_1;
        int derive1_2;
    
        // 自身定义的虚函数
        virtual void derive1_fun1() {}
        virtual void derive1_fun2() {}
    };

    只需要看看偏移就行了:

    只需知道: 谁有虚函数表, 谁就往前靠!


    C++中父子对象指针间的转换与函数调用

    讲了那么多布局方面的东东, 终于到了尾声, 好累呀!!!

    通过前面的讲解内容, 大家至少应该明白了各类情况下类对象的内存布局了. 如果还不会…..呃….. !@#$%^&*

    进入正题~

    由于继承完全拥有父类的所有, 包括数据成员与虚函数表, 所以:把一个继承类强制转换为一个基类是完全可行的.

    如果有一个Derive1的指针, 那么:

    • 得到Base1的指针: Base1* pb1 = pd1;
    • 得到Base2的指针: Base2* pb2 = pd1;
    • 得到Base3的指针: Base3* pb3 = pd1;

    非常值得注意的是:

    这是在基类与继承类之间的转换, 这种转换会自动计算偏移! 按照前面的布局方式!
    也就是说: 在这里极有可能: pb1 != pb2 != pb3 ~~, 不要以为她们都等于 pd1!

    至于函数调用, 我想, 不用说大家应该知道了:

    1. 如果不是虚函数, 直接调用指针对应的基本类的那个函数
    2. 如果是虚函数, 则查找虚函数表, 并进行后续的调用. 虚函数表在定义一个时, 编译器就为我们创建好了的. 所有的, 同一个类, 共用同一份虚函数表.

    用C语言完全模拟C++虚函数表的实现与运作方式

    如果对前面两大节的描述仔细了解了的话, 想用C语言来模拟C++的虚函数以及多态, 想必是轻而易举的事情鸟!

    前提

    但是, 话得说在前面, C++的编译器在生成类及对象的时候, 帮助我们完成了很多事件, 比如生成虚函数表!
    但是, C语言编译器却没有, 因此, 很多事件我们必须手动来完成, 包括但不限于:

    • 手动构造父子关系
    • 手动创建虚函数表
    • 手动设置__vfptr并指向虚函数表
    • 手动填充虚函数表
    • 若有虚函数覆盖, 还需手动修改函数指针
    • 若要取得基类指针, 还需手动强制转换
    • ……

    总之, 要想用C语言来实现, 要写的代码绝对有点复杂.

    C++原版调用

    接下来, 我们都将以最后那个, 最繁杂的那个3个基类的实例来讲解, 但作了一些简化与改动:

    • 用构造函数初始化成员变量
    • 减少成员变量的个数
    • 减少虚函数的个数
    • 调用函数时产生相关输出
    • Derive1增加一个基类虚函数覆盖

    以下是对类的改动, 很少:

    class Base1
    {
    public:
        Base1() : base1_1(11) {}
        int base1_1;
        virtual void base1_fun1() {
            std::cout << "Base1::base1_fun1()" << std::endl;
        }
    };
    
    class Base2
    {
    public:
        Base2() : base2_1(21) {}
        int base2_1;
    };
    
    class Base3
    {
    public:
        Base3() : base3_1(31) {}
        int base3_1;
        virtual void base3_fun1() {
            std::cout << "Base3::base3_fun1()" << std::endl;
        }
    };
    
    class Derive1 : public Base1, public Base2, public Base3
    {
    public:
        Derive1() : derive1_1(11) {}
        int derive1_1;
    
        virtual void base3_fun1() {
            std::cout << "Derive1::base3_fun1()" << std::endl;
        }
        virtual void derive1_fun1() {
                std::cout << "Derive1::derive1_fun1()" << std::endl;
        }
    };

    为了看到多态的效果, 我们还需要定义一个函数来看效果

    void foo(Base1* pb1, Base2* pb2, Base3* pb3, Derive1* pd1)
    {
        std::cout << "Base1::\n"
            << "    pb1->base1_1 = " << pb1->base1_1 << "\n"
            << "    pb1->base1_fun1(): ";
        pb1->base1_fun1();
    
        std::cout << "Base2::\n"
            << "    pb2->base2_1 = " << pb2->base2_1
            << std::endl;
    
        std::cout << "Base3::\n"
            << "    pb3->base3_1 = " << pb3->base3_1 << "\n"
            <<"    pb3->base3_fun1(): ";
        pb3->base3_fun1();
    
        std::cout << "Derive1::\n"
            << "    pd1->derive1_1 = " << pd1->derive1_1<< "\n"
            <<"    pd1->derive1_fun1(): ";
        pd1->derive1_fun1();
        std::cout<< "    pd1->base3_fun1(): ";
        pd1->base3_fun1();
    
        std::cout << std::endl;
    }

    调用方式如下:

    Derive1 d1;
    foo(&d1, &d1, &d1, &d1);

    输出结果:

    可以看到输出结果全部正确(当然了! :-), 哈哈~
    同时注意到 pb3->base3_fun1() 的多态效果哦!


    用C语言来模拟

    必须要把前面的理解了, 才能看懂下面的代码!

    为了有别于已经完成的C++的类, 我们分别在类前面加一个大写的C以示区分(平常大家都是习惯在C++写的类前面加C, 今天恰好反过来, 哈哈).

    C语言无法实现的部分

    C/C++是两个语言, 有些语言特性是C++专有的, 我们无法实现! 不过, 这里我是指调用约定, 我们应该把她排除在外.

    对于类的成员函数, C++默认使用__thiscall, 也即this指针通过ecx传递, 这在C语言无法实现, 所以我们必须手动声明调用约定为:

    1. __stdcall, 就像微软的组件对象模型那样
    2. __cdecl, 本身就C语言的调用约定, 当然能使用了.

    上面那种调用约定, 使用哪一种无关紧要, 反正不能使用__thiscall就行了.

    因为使用了非__thiscall调用约定, 我们就必须手动传入this指针, 通过成员函数的第1个参数!

    从最简单的开始: 实现 Base2

    由于没有虚函数, 仅有成员变量, 这个当然是最好模拟的咯!

    struct CBase2
    {
        int base2_1;
    };

    有了虚函数表的Base1, 但没被覆盖

    下面是Base1的定义, 要复杂一点了, 多一个__vfptr:

    struct CBase1
    {
        void** __vfptr;
        int base1_1;
    };

    因为有虚函数表, 所以还得单独为虚函数表创建一个结构体的哦!
    但是, 为了更能清楚起见, 我并未定义前面所说的指针数组, 而是用一个包含一个或多个函数指针的结构体来表示!
    因为数组能保存的是同一类的函数指针, 不太很友好!
    但他们的效果是完全一样的, 希望读者能够理解明白!

    struct CBase1_VFTable
    {
        void(__stdcall* base1_fun1)(CBase1* that);
    };

    注意: base1_fun1 在这里是一个指针变量!
    注意: base1_fun1 有一个CBase1的指针, 因为我们不再使用__thiscall, 我们必须手动传入! Got it?

    Base1的成员函数base1_fun1()我们也需要自己定义, 而且是定义成全局的:

    void __stdcall base1_fun1(CBase1* that)
    {
    std::cout << “base1_fun1()” << std::endl;
    }
    有虚函数覆盖的Base3

    虚函数覆盖在这里并不能体现出来, 要在构造对象初始化的时候才会体现, 所以: base3其实和Base1是一样的.

    struct CBase3
    {
        void** __vfptr;
        int base3_1;
    };
    struct CBase3_VFTable
    {
        void(__stdcall* base3_fun1)(CBase3* that);
    };

    Base3的成员函数:

    void __stdcall base3_fun1(CBase3* that)
    {
        std::cout << "base3_fun1()" << std::endl;
    }

    定义继承类CDerive1

    相对前面几个类来说, 这个类要显得稍微复杂一些了, 因为包含了前面几个类的内容:

    struct CDerive1
    {
        CBase1 base1;
        CBase3 base3;
        CBase2 base2;
    
        int derive1_1;
    };

    特别注意: CBase123的顺序不能错!

    另外: 由于Derive1本身还有虚函数表, 而且所以项是加到第一个虚函数表(CBase1)的后面的, 所以此时的CBase1::__vfptr不应该单单指向CBase1_VFTable, 而应该指向下面这个包含Derive1类虚函数表的结构体才行:

    struct CBase1_CDerive1_VFTable
    {
        void (__stdcall* base1_fun1)(CBase1* that);
        void(__stdcall* derive1_fun1)(CDerive1* that);
    };

    因为CDerive1覆盖了CBase3的base3_fun1()函数, 所以不能直接用Base3的那个表:

    struct CBase3_CDerive1_VFTable
    {
        void(__stdcall* base3_fun1)(CDerive1* that);
    };

    Derive1覆盖Base3::base3_fun1()的函数以及自身定义的derive1_fun1()函数:

    void __stdcall base3_derive1_fun1(CDerive1* that)
    {
        std::cout << "base3_derive1_fun1()" << std::endl;
    }
    
    void __stdcall derive1_fun1(CDerive1* that)
    {
        std::cout << "derive1_fun1()" << std::endl;
    }

    构造各类的全局虚函数表

    由于没有了编译器的帮忙, 在定义一个类对象时, 所有的初始化工作都只能由我们自己来完成了!

    首先构造全局的, 被同一个类共同使用的虚函数表!

    // CBase1 的虚函数表
    CBase1_VFTable __vftable_base1;
    __vftable_base1.base1_fun1 = base1_fun1;
    
    // CBase3 的虚函数表
    CBase3_VFTable __vftable_base3;
    __vftable_base3.base3_fun1 = base3_fun1;
    然后构造CDerive1和CBase1共同使用的虚函数表:
    
    // CDerive1 和 CBase1 共用的虚函数表
    CBase1_CDerive1_VFTable __vftable_base1_derive1;
    __vftable_base1_derive1.base1_fun1 = base1_fun1;
    __vftable_base1_derive1.derive1_fun1 = derive1_fun1;
    

    再构造CDerive1覆盖CBase3后的虚函数表: 注意: 数覆盖会替换原来的函数指针

    CBase3_CDerive1_VFTable __vftable_base3_derive1;
    __vftable_base3_derive1.base3_fun1 = base3_derive1_fun1;
    

    开始! 从CDerive1构造一个完整的Derive1类
    先初始化成员变量与__vfptr的指向: 注意不是指错了!

    CDerive1 d1;
    d1.derive1 = 1;
    
    d1.base1.base1_1 = 11;
    d1.base1.__vfptr = reinterpret_cast<void**>(&__vftable_base1_derive1);
    
    d1.base2.base2_1 = 21;
    
    d1.base3.base3_1 = 31;
    d1.base3.__vfptr = reinterpret_cast<void**>(&__vftable_base3_derive1);

    由于目前的CDerive1是我们手动构造的, 不存在真正语法上的继承关系, 如要得到各基类指针, 我们就不能直接来取, 必须手动根据偏移计算:

    char* p = reinterpret_cast<char*>(&d1);
    Base1* pb1 = reinterpret_cast<Base1*>(p + 0);
    Base2* pb2 = reinterpret_cast<Base2*>(p + sizeof(CBase1) + sizeof(CBase3));
    Base3* pb3 = reinterpret_cast<Base3*>(p + sizeof(CBase1));
    Derive1* pd1 = reinterpret_cast<Derive1*>(p);
    

    真正调用:

    foo(pb1, pb2, pb3, pd1);

    调用结果:

    结果相当正确!!!

    源代码

    我以为我把源代码搞丢了,结果过了一年多发现其实并没有。— 2015-12-24(每个圣诞我都在写代码)

    有两个,忘了区别了:Source1.cppSource2.cpp.


    Reference

    本文转载自:https://blog.twofei.com/496/

    展开全文
  • C++虚函数表实例分析

    2021-05-22 18:20:26
    Base中虚函数表结构: Derive中虚函数表结构: (4)验证 运行上面代码结果: size of Base: 8 base::f derive::g base::h 说明Derive的虚函数表结构跟上面分析的是一致的: d对象的首地址就是vptr指针的地址-pvptr,...

    我们先来看看代码:

    #include

    using namespace std;

    class Base {

    public:

    virtual void f() {cout<

    virtual void g() {cout<

    virtual void h() {cout<

    };

    class Derive : public Base{

    public:

    void g() {cout<

    };

    //可以稍后再看

    int main () {

    cout<

    typedef void(*Func)(void);

    Base b;

    Base *d = new Derive();

    long* pvptr = (long*)d;

    long* vptr = (long*)*pvptr;

    Func f = (Func)vptr[0];

    Func g = (Func)vptr[1];

    Func h = (Func)vptr[2];

    f();

    g();

    h();

    return 0;

    }

    470aee9138cb3e8f3d519abb6f51dc60.png

    都知道C++中的多态是用虚函数实现的: 子类覆盖父类的虚函数, 然后声明一个指向子类对象的父类指针, 如Base *b = new Derive();

    当调用b->f()时, 调用的是子类的Derive::f()。

    这种机制内部由虚函数表实现,下面对虚函数表结构进行分析,并且用GDB验证。

    1. 基础知识:

    (1) 32位os 指针长度为4字节, 64位os 指针长度为8字节, 下面的分析环境为64位 linux & g++ 4.8.4.

    (2) new一个对象时, 只为类中成员变量分配空间, 对象之间共享成员函数。

    2. _vptr

    运行下上面的代码发现sizeof(Base) = 8, 说明编译器在类中自动添加了一个8字节的成员变量, 这个变量就是_vptr, 指向虚函数表的指针。

    _vptr有些文章里说gcc是把它放在对象内存的末尾,VC是放在开始, 我编译是用的g++,验证了下是放在开始的:

    验证代码:取对象a的地址与a第一个成员变量n的地址比较,如果不等,说明对象地址开始放的是_vptr. 也可以用gdb直接print a 会发现_vptr在开始

    class A

    {

    public:

    int n;

    virtual void Foo(void){}

    };

    int main()

    {

    A a;

    char *p1 = reinterpret_cast(&a);

    char *p2 = reinterpret_cast(&a.n);

    if(p1 == p2)

    {

    cout<

    }else

    {

    cout<

    }

    return 1;

    }

    (3) 虚函数表

    包含虚函数的类才会有虚函数表, 同属于一个类的对象共享虚函数表, 但是有各自的_vptr.

    虚函数表实质是一个指针数组,里面存的是虚函数的函数指针。

    Base中虚函数表结构:

    e7890a626467c7668d8f44ddd67b0fc5.png

    Derive中虚函数表结构:

    f21d5a5f37abc868aea3c6b4e7176ad2.png

    (4)验证

    运行上面代码结果:

    size of Base: 8

    base::f

    derive::g

    base::h

    说明Derive的虚函数表结构跟上面分析的是一致的:

    d对象的首地址就是vptr指针的地址-pvptr,

    取pvptr的值就是vptr-虚函数表的地址

    取vptr中[0][1][2]的值就是这三个函数的地址

    通过函数地址就直接可以运行三个虚函数了。

    函数表中Base::g()函数指针被Derive中的Derive::g()函数指针覆盖, 所以执行的时候是调用的Derive::g()

    (5)多继承

    3b6b4c4f3637e2a013bf5a0a2692bc9c.png

    b7177ef7830d04db27db370d71224a1b.png

    附 GDB调试:

    (1) #生成带有调试信息的可执行文件

    g++ test.cpp -g -o test

    (2) #载入test

    gdb test

    (3) #列出Base类代码

    (gdb) list Base1 #include

    2

    3 using namespacestd;4

    5 classBase {6 public:7 virtual void f() {cout<

    (4) #查看Base函数地址

    (gdb) info line7Line7 of "test.cpp" starts at address 0x400ac8 <:f> and ends at 0x400ad4 <:f>.

    (gdb) info line8Line8 of "test.cpp" starts at address 0x400af2 <:g> and ends at 0x400afe <:g>.

    (gdb) info line9Line9 of "test.cpp" starts at address 0x400b1c <:h> and ends at 0x400b28 <:h>.

    (5)#列出Derive代码

    (gdb) list Derive7 virtual void f() {cout<

    12 class Derive : publicBase{13 public:14 void g() {cout<

    (6)#查看Derive函数地址

    (gdb) info line14Line14 of "test.cpp" starts at address 0x400b46 <:g> and ends at 0x400b52 <:g>.

    (7)#start执行程序,n单步执行

    (gdb) start

    Temporary breakpoint1, main () at test.cpp:19

    19 cout<

    (gdb) n

    size of Base:8

    22Base b;

    (gdb)23 Base *d = newDerive();

    (gdb)25 long* pvptr = (long*)d;

    (gdb)26 long* vptr = (long*)*pvptr;

    (gdb)27 Func f = (Func)vptr[0];

    (gdb)28 Func g = (Func)vptr[1];

    (gdb)29 Func h = (Func)vptr[2];

    (gdb)31f();

    (gdb)

    (8) #print d对象, 0x400c90为成员变量_vptr的值,也就是函数表的地址

    (gdb) p*d

    $4 = {_vptr.Base = 0x400c90 }

    (gdb) p vptr

    $6 = (long *) 0x400c90 (9) #查看函数表值,与之前查看函数地址一致

    (gdb) p (long*)vptr[0]

    $9 = (long *) 0x400ac8 <:f>(gdb) p (long*)vptr[1]

    $10 = (long *) 0x400b46 <:g>(gdb) p (long*)vptr[2]

    $11 = (long *) 0x400b1c <:h>

    367b181eb47f2cd9c5f5835bda75eeaa.png

    0b1331709591d260c1c78e86d0c51c18.png

    展开全文
  • 虚函数指针和虚函数表 虚函数表的定义 多态是由虚函数实现的,而虚函数主要是通过虚函数表(V-Table)来实现的。 如果一个类中包含虚函数(virtual修饰的函数),那么这个类就会包含一张虚函数表(vftbl),虚函数表存储的...

    虚函数指针和虚函数表

    虚函数表的定义

    多态是由虚函数实现的,而虚函数主要是通过虚函数表(V-Table)来实现的。

    如果一个类中包含虚函数(virtual修饰的函数),那么这个类就会包含一张虚函数表(vftbl),虚函数表存储的每一项是一个虚函数的地址。在一个对象的内存布局中,指向这张虚函数表的指针(vfptr)位于最前端。如下图:

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

    对于如下UML的类:在普通继承情况,派生类没有重写基类的虚函数:

    体现在代码上为:

    #include <iostream>
    
    using namespace std;
    
    class Base
    {
    public:
        virtual void f()
        {
            cout << "Base:f()" << endl;
        }
        virtual void g()
        {
            cout << "Base:g()" << endl;
        }
        virtual void h()
        {
            cout << "Base:h()" << endl;
        }
    };
    
    class Derive : public Base
    {
    public:
        virtual void f1()
        {
            cout << "Derive:f1()" << endl;
        }
        virtual void g1()
        {
            cout << "Derive:g1()" << endl;
        }
        virtual void h1()
        {
            cout << "Derive:h1()" << endl;
        }
    };
    
    int main() {
        Base base;
        Derive derive;
    
    }

    使用lldb查看虚函数指针和虚函数表内容:

    根据结果,可以得知其虚函数表如下图:

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

    对于如下UML的类:在普通继承情况,派生类重写了基类的虚函数( f() ):

    代码为:

    #include <iostream>
    
    using namespace std;
    
    class Base
    {
    public:
        virtual void f()
        {
            cout << "Base:f()" << endl;
        }
        virtual void g()
        {
            cout << "Base:g()" << endl;
        }
        virtual void h()
        {
            cout << "Base:h()" << endl;
        }
    };
    
    class Derive : public Base
    {
    public:
        virtual void f()
        {
            cout << "Derive:f()" << endl;
        }
        virtual void g1()
        {
            cout << "Derive:g1()" << endl;
        }
        virtual void h1()
        {
            cout << "Derive:h1()" << endl;
        }
    };
    
    int main() {
        Base base;
        Derive derive;
    
    }

    结果为:

    绘制成图,为:

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

    对于如下UML的类:在多重继承情况,派生类没有重写了基类的虚函数:

    代码为:

    #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 f1()
        {
            cout << "Derive:f1()" << endl;
        }
        virtual void g1()
        {
            cout << "Derive:g1()" << endl;
        }
    };
    
    int main() {
        Derive derive;
    
    }

    结果为:

    绘制成图,为:

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

    对于如下UML的类:在多重继承情况,派生类没有重写了基类的虚函数:

    代码为:

    #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;
        }
    };
    
    int main() {
        Derive derive;
    
    }

    结果为:

    绘制成图,为:

    展开全文
  • ​目录 C++类的虚函数表和虚函数在内存中的位置 虚函数表和虚函数在内存中的位置说明 结论 原文C++类的虚函数表和...因而通过new 出来的对象的虚函数表指针位于堆,声名对象的虚函数表指针位于栈。 总结: 1...
  • 版权声明:本文为CSDN博主「Runner_of_nku」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。...显而易见,虚函数表存放在全局数据区. 下面开始试验 class A{ public: ...
  • 理解 C++ 虚函数表

    2021-03-16 14:47:52
    引言虚表是 C++ 中一个十分重要的概念,面向对象编程的多态性在 C++ 中的实现全靠虚表来实现。在聊虚表之前我们先回顾一下什么事多态性。多态实际上就是让一个父类指针,通过赋予子类对象的地址,可以呈现出多种形态...
  • 为了实现虚函数,C++ 使用了虚函数表来达到延迟绑定的目的。虚函数表在动态/延迟绑定行为中用于查询调用的函数。 尽管要描述清楚虚函数表的机制会多费点口舌,但其实其本身还是比较简单的。 首先,每个包含虚...
  • C++ 虚函数表 vfptr详解

    2021-04-08 10:51:54
    大家都应该知道C++的精髓是虚函数吧? 虚函数带来的好处就是: 可以定义一个基类的指针, 其指向一个继承类, 当通过基类的指针去调用函数时, 可以在运行时决定该调用基类的函数还是继承类的函数. 虚函数是实现多态(动态...
  • 虚函数表和虚表指针

    多人点赞 2021-01-06 22:50:15
    虚函数表指针 虚函数表 想要理解其中的结构,还是要从内存布局看起 下面都是用debug的方式,查看其中的布局大小 一定要理解下面的四段代码 1,普通类的内存布局 代码: 因为普通函数并不占内存,所以此时...
  • 一、没有虚函数的对象,它的内存分布。 #include <stdio.h> #include <stddef.h> // 定义了offsetof宏 class Base { public: int m_val1; int m_val2; void fun() {} }; int main() { Base b; ...
  • C++多态中的虚函数表

    2021-07-18 21:09:02
    C++中的虚函数表 之前的 C++ 继承中已经说过多态基本概念,这里不再赘述。再下面多处给出了类实例对象的内存布局,查看内存的布局时,使用 VS 工具 /d1 reportAllClassLayout 进行查看,关于这个工具的详细介绍,请...
  • 众所周知,每个带有虚函数的类,或者继承具有虚函数类的对象,本身都是会有一个虚函数表的,前者为自身创建的,后者为继承,且虚函数表的指针位置位于类的首四位地址,如下图所示! 如何获取虚函数表地址?看下面...
  • 其实大家应该都或多或少地知道:`虚函数是通过虚函数表实现的`。但是呢,可能和我之前一样,知道大概是怎样的,但是没有通过代码真正地运行测试。最近我也在网上查看了一番,发现有的文章写得很好,但是代码上面可能...
  • 目录 ...因而通过new 出来的对象的虚函数表指针位于堆,声名对象的虚函数表指针位于栈。 总结: 1.虚函数表指针位置取决于对象在哪。如果是new的对象,则存在堆上,如果是直接声明,则存在栈上.
  • 1.虚函数表指针的位置分析 一个类有虚函数的话,会产生一个虚函数表 而生成这个类的对象的时候,这个对象就会产生一个指针,指向虚函数表的开始位置,这个指针就是虚函数指针(vptr),有种类似于这个对象的成员...
  • 本博客主要通过查看类的内容的变化,深入探讨有关指针和虚表的问题。 一、继承产生的基类指针和基类 如下代码:写一个棱形继承,父类Base,子类Son1和Son2继承Base,又来一个类Grandson继承Son1和Son2...
  • 目录引用的深入理解继承\组合构造函数谁先调用转换函数malloc的内部实现原理explicitnamespace虚函数指针和虚函数表 引用的深入理解 引用是啥学C++的都知道,那他和指针有何区别?引用不可以更改指向的对象,引用第...
  • C++——虚函数表

    2021-03-21 12:49:51
    对原文的内容加了一些自己的理解,测试编译环境为win10+vs2015 前言 C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型的指针指向其子类的实例,然后通过父类的指针调用实际...虚函数表
  • 1.多重继承可能产生多个虚函数表 代码示例:会产生多个虚函数表 #include <iostream> #include <string> using namespace std; class BaseA { public: virtual void funcA() { cout <<...
  • 继承和基类继承:在继承定义中包含了virtual关键字的继承关系;基类:在继承体系中的通过virtual继承而来的基类,需要注意的是:class CSubClass : public virtual CBase {}; 其中CBase称之为CSubClass的...
  • 继承机制是对象对象程序设计使代码可以复用的重要手段,允许程序员在保持原有类特性的基础上进行扩展,产生新类,称为派生类,以前的学的都是函数复用,继承是类设计层次的复用。 继承的定义 Base类是父类,也称为...
  • 本文只是拿 Java 代码做说明的例子,不代表Java的vtable是这样实现的虚函数表,又称 virtual method table (VMT), virtual function table, virtual call table, dispatch table, vtable, or vftable。是一种用于...
  • VS查看内存布局的方法:Properties - Configuration Properties - C/C++ - Command Line - Addtion Options添加/d1 reportAllClassLayout。这样会在Debug的输出中显示所有类的内存布局。如果要指定只查看某个类的,...
  • 总结一下派生类虚函数表的生成过程: 首先派生类会将基类的虚函数表拷贝过来 如果派生类完成了对虚函数的重写,则用重写后的虚函数覆盖掉虚函数表中继承下来的基类虚函数 如果派生类自己又新增了虚函数,则添加在...
  • C++ 虚函数表 Hook

    2021-07-11 17:08:07
    当C++类中函数虚函数时,为了实现多态,C++类最开始地方会包含一个指针,该指针指向虚函数表,当我们修改这虚函数表里地址就达到了实现虚函数表Hook的目的。 代码如下: #include "VTblHook.h" #include <...
  • 先上结论:C++虚函数表保存在.rdata只读数据段。编译时期由编译器确定虚函数表虚函数表属于类,类的所有对象共享这个类的虚函数表。 c/c++的内存分配 栈(stack):又称堆栈,栈是由编译器自动分配释放,存放...
  • 文章目录一、虚函数表二、首先讲一下结论:三、证明 一、虚函数表 虚函数表是一块连续的内存,每个内存单元中记录一个JMP指令的地址。 二、首先讲一下结论: 首先,虚函数表在编译时候就已经确定; 对象在构造前...
  • 在c++中,对于一个不含虚函数的类,它的大小为变量的大小,不包含在类里面的非虚函数。 case 1:类中只有变量 class Base1 { public: int base1_1; int base1_2; // void foo(){} }; 比如对于Base1来说,有无...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 123,907
精华内容 49,562
关键字:

虚函数表