首页 > 编程语言 >【C++】多态(下)

【C++】多态(下)

时间:2023-05-18 10:33:06浏览次数:72  
标签:虚表 VF int 多态 C++ 地址 PTR 指针

@TOC

1.单继承中的虚函数表

整体代码

#include<iostream>
using namespace std;
class Base
{
public:
    virtual void Func1()
    {
        cout << "Base::Func1()" << endl;
    }
    virtual void Func2()
    {
        cout << "Base::Func2()" << endl;
    }
    void Func3()
    {
        cout << "Base::Func3()" << endl;
    }
private:
    int _b = 1;
};
class Derive : public Base
{
public:
    virtual void Func1()
    {
        cout << "Derive::Func1()" << endl;
    }
    virtual void Func4()
    {
        cout << "Base::Func4()" << endl;
    }
private:
    int _d = 2;
};

typedef void(*VF_PTR)();    
//  typedef void(*)() VF_PTR;
void PrintVFTable(VF_PTR table[])//函数指针数组
{
    int i = 0;
    for (i = 0; table[i] != nullptr; i++)
    {
        printf("[%d]:%p\n",i, table[i]);
        VF_PTR f = table[i];
        f();
    }
}
int main()
{
    Base b;
    Derive d;
    PrintVFTable((VF_PTR*)*(int*)&b);
    cout << endl;
    PrintVFTable((VF_PTR*)*(int*)&d);
    return 0;
}

在子类中实现一个虚函数Func4,但是不构成重写


【C++】多态(下)_虚表


【C++】多态(下)_函数指针_02

Func4函数并没有进入虚表中


【C++】多态(下)_虚表_03

通过查询内存发现,虚表指针中存在三个地址,而其中两个正好为监视中的两个地址 猜测 0x00c4146a 就是Func4的地址

用程序打印虚表

虚表本质是一个函数指针数组

【C++】多态(下)_函数指针_04

VS中在数组最后放了一个nullptr,这样就可以解决在不同虚表中的个数不同的问题


【C++】多态(下)_虚表_05

typedef一个函数指针 为VF_PTR


正常来说 要写成将VF_PTR放在后面

【C++】多态(下)_虚表_06

但是由于函数指针的特殊性,定义变量都要放在中间


如何寻找到虚表地址

想要虚表的地址,就可以通过找到虚表的指针 而这个虚表指针在对象中,这个指针在对象的前4个(32位)或者8个字节(64位)上面

【C++】多态(下)_强制转换_07


以32位为例,如何取对象的前4个字节

强制转换为int*


【C++】多态(下)_函数指针_08

* (int* )&b 首先取到Base* ,将其强制转换为int*,代表前四个字节的地址,再解引用是int类型,把前四个字节取出来 但是由于PrintVFTable函数参数是 函数指针数组


(VF_PTR*) * (int *)&b 如果这个数组是int类型,就需要 一个int * 指针去指向 同理 ,该数组为 VF_PTR类型,需要一个VF_PTR *指针去指向 所以需将 int 类型 再次强制转换为 VF_PTR * ,使其指向这个数组


缺陷 但是这种写法具有一定的局限性,只能在32位跑,因为32位指针大小为4个字节 而64位下就不行了,64位下指针大小为8个字节


【C++】多态(下)_强制转换_09

运行程序打印虚表,确实了解到多了一个地址


【C++】多态(下)_虚表_10


【C++】多态(下)_虚表_11

把虚表的地址拿出来赋给函数指针,用函数指针去调用函数 这里发现 监视中没有出现的地址确实是Func4函数的地址

虚表存在哪里?

【C++】多态(下)_函数指针_12

由于常量区地址与虚表的地址最为接近,所以说明虚表在常量区/代码段上

2.多继承中的虚函数表

整体代码

class Base1 {
public:
    virtual void func1() { cout << "Base1::func1" << endl; }
    virtual void func2() { cout << "Base1::func2" << endl; }
private:
    int b1;
};
class Base2 {
public:
    virtual void func1() { cout << "Base2::func1" << endl; }
    virtual void func2() { cout << "Base2::func2" << endl; }
private:
        int b2;
};
class Derive : public Base1, public Base2 {
public:
    virtual void func1() { cout << "Derive::func1" << endl; }
    virtual void func3() { cout << "Derive::func3" << endl; }
private:
    int d1;
};
typedef void(*VF_PTR)();    
//  typedef void(*)() VF_PTR;
void PrintVFTable(VF_PTR table[])//函数指针数组
{
    int i = 0;
    for (i = 0; table[i] != nullptr; i++)
    {
        printf("[%d]:%p->", i, table[i]);
        VF_PTR f = table[i];
        f();
    }
}
int main()
{
    Derive d;
    PrintVFTable(  (VF_PTR*) *(int*) &d);
    cout << endl;
    /*PrintVFTable((VF_PTR*)*(int*)( (char*)&d+ sizeof(Base1) ) );*/
    Base2* ptr2 = &d;
    PrintVFTable((VF_PTR*)*(int*)(ptr2));
    return 0;
}

寻找虚表地址

【C++】多态(下)_强制转换_13

Derive 作为Base1 和Base2的子类,所以Derive内部有两张虚表


【C++】多态(下)_虚表_14

正常来说,Derive内部还存在一个func3函数,这个函数放在哪里了呢? 借助打印虚表来查看,这里的打印虚表依旧可以使用单继承中的那个


【C++】多态(下)_强制转换_15


【C++】多态(下)_强制转换_16

base1的虚表指针 正好在对象的前4个字节处,直接可以使用求出虚表指针 去指向base1的虚表


方法1 : base2的虚表指针 需要加上base1的大小

【C++】多态(下)_强制转换_17

但是这里要注意一个问题,若写成 PrintVFTable((VF_PTR*)(int)( &d+ sizeof(Base1) ) ) 写的并不对,d本身是一个Derive类型,&d后变为Derive* 的一个指针,+1 跳转的是Derive类型的字节大小 而该设计想要每次+1跳转1个字节,所以需要强制转换char*


方法2 :切片自动偏移

【C++】多态(下)_强制转换_18


【C++】多态(下)_函数指针_19


【C++】多态(下)_函数指针_20

两种方法的结果都是一样的

注意事项

【C++】多态(下)_虚表_21

多继承派生类增加的的虚函数在第一个虚表中

多继承重写后的func1地址为什么不同?

【C++】多态(下)_强制转换_22


ptr1调用函数——一次jmp

【C++】多态(下)_强制转换_23

找到 Base1虚表里的地址 只需要jmp一次 就可以找到实际真正执行的函数地址ptr1调用地址属于正常调用


ptr2 调用函数——多次jmp

【C++】多态(下)_函数指针_24

ptr2调用地址,需要 多次jmp 才能找到真正的函数地址

【C++】多态(下)_函数指针_25

ecx存的是this指针ecx,8 目的是修正this指针的位置 最终Base1和Base2都是执行同一个函数的指令


【C++】多态(下)_函数指针_26

  • ptr1->func1 调用的是子类的func1函数,ptr1指向调用对象的开始
  • ptr2并没有指向子类对象的开始,此时调用子类对象的func1函数,this指针指向中间的位置不对了,所以需要修正this指针,使之指向子类对象开始的地方

标签:虚表,VF,int,多态,C++,地址,PTR,指针
From: https://blog.51cto.com/u_15787387/6295520

相关文章

  • c++打卡第二十九天
    模板编程对于模板编程,写template<typenameT>一、函数模板编程1、编辑模板表明返回值T或者无返回值+函数名(T&变量) 2、例题描述请使用模板参数设计实现双倍功能函数,函数功能要求实现返回值为输入参数的两倍,函数参数应能适应整型、浮点型、双精度型等各种类型,返回值类型与......
  • 输入输出流(C++)
    一、问题描述定义一个Dog类,包括体重和年龄两个数据成员及其成员函数,声明一个实例dog1,体重5,年龄10,使用I/O流把dog1的状态写入磁盘文件。再声明一个实例dog2,通过读取文件dog1的状态赋给dog2。分别用文本方式和二进制方式操作文件。二、代码实现1#include<fstream>2#includ......
  • 2654. 使数组所有元素变成 1 的最少操作次数(c++,gcd性质)
    题目链接:2654.使数组所有元素变成1的最少操作次数方法一:计算最短的gcd为1的子数组解题思路本题目标:使得所有的数组元素都变为\(1\),通过求相邻元素\(gcd\)将其赋值给一方的方式;思路:若想操作数最少,那么就是不为\(1\)的数\(x\)和1求\(gcd\),即\(x=gcd(x,1)\),......
  • c++打卡练习(33)
    歌星大赛,十个评委打分,去掉一个最高分,去掉一个最低分,求剩下的八个评分的平均分,作为选手的最终分数流程图:伪代码:源代码:#include<iostream>usingnamespacestd;intmain(){ inta[10],b[8]; inti,j,k,t,sum=0,Ave,max,min; cout<<"输入十个正整数"<<endl; for(i=0;i<10;i++){ ......
  • C++调用python过程+Anaconda使用arcpy包踩的坑
    C++调python(python文件包含第三方库):工具:VS2017QT5插件PycharmAnaconda1.下载Anaconda,配置一个虚拟环境2.将这个环境里的DLLs和Lib包以及相应py文件,放至C++项目生成.exe文件同级目录下 3.将include和libs放在项目某文件夹下,在VS里添加附加包含目录、附加库目录和附加依赖......
  • Mac 配置 OpenCV C++ 版本
    今天紀錄一下如何在Mac上安裝OpenCVforC++開發環境使用Brew安装,pkgconfig检测,2023.5.17Macx86(Intel),MacM1(Applesilicon)和Ubuntu也適用此筆記用OpenCV4.7.0_4版本做範例1.安装cmake与pkg-config如果您的 Mac 沒有cmake,pkg-config請先......
  • C++进阶学习(三)constexpr关键字、值类别与decltype关键字、lambda表达式
    五、constexpr说明符constexpr说明符声明该变量或函数在编译期进行求值,从而适用于需要编译器常量表达式的地方在变量声明constexpr时,对象或非静态成员函数蕴含const,函数或静态成员变量蕴含inlineconstexpr变量必须立刻被初始化constexprinta=5;//a=6;/*error*/......
  • C++
    复数类的运算#include<iostream>usingnamespacestd;classComplex{public:Complex(doubler=0,doublei=0):real(r),imag(i){}friendComplexoperator+(Complexc1,Complexc2)//重载双目运算符'+'{Complexc3;......
  • c++unique
    #include<iostream>#include<cstdio>#include<cmath>#include<algorithm>#include<cstring>usingnamespacestd;intn,a[5211314],len;intmain(){ cin>>n; for(inti=1;i<=n;++i){ cin>>a[i]......
  • C++ 智能指针
    在介绍智能指针之前,先来看原始指针的一些不便之处:它的声明不能指示所指到底是单个对象还是数组。它的声明没有告诉你用完后是否应该销毁它,即指针是否拥有所指之物。如果你决定你应该销毁指针所指对象,没人告诉你该用delete还是其他析构机制(比如将指针传给专门的销毁函数)......