C++虚函数的实现

虚函数的意义

首先我们要先知道继承,继承之后可以使用虚拟标识符,表示函数的绑定推迟到运行时才决定,这样就实现了多态,将决定使用那个类的方法交给运行函数时调用的对象的类型决定使用那个方法。

C++对象内存布局

了解C++对象内存布局,有三种比较常见的方法

  1. 使用成员变量的偏移,在C++中可以使用offsetof(对象, 成员变量)和sizeof(对象)使用
  2. 使用VS自带的开发者工具查看对象内存布局
  3. 使用调试器查看,vs中在debug中可以查看临时变量和成员变量内存布局

方法一

实现如下:

// 使用方法1查看对象的内存结构
class Base {
public:
    int base_1;
    int base_2;
    int base_3;

};
int main()
{
    cout << "Base的大小:" << sizeof(Base) << endl;
    cout << "Base中base_1的开始位置:" << offsetof(Base, base_1) << endl;
    cout << "Base中base_2的开始位置:" << offsetof(Base, base_2) << endl;
    cout << "Base中base_3的开始位置:" << offsetof(Base, base_3) << endl;
    return 0;
}
// 输出:
Base的大小:12
Base中base_1的开始位置:0	// 第一个int对象大小为4字节,即内存的第0~4字节分配给一个int
Base中base_2的开始位置:4	// 同上,第二个int对象在内存的第4~8字节
Base中base_3的开始位置:8	// 第三个int从8~12
// 以上说明了只有数据成员的对象内存布局只有他的数据成员构成。

方法二

实现

class Base {
public:
    int base_1;
    int base_2;
    int base_3;
    void func(){}
}

使用vs自带的开发者工具查看对象内u才能布局(这里要X64的,因为我的代码是x64的,对齐是以8字节,也可以使用X86的也就是32位,对齐是以4字节的),也可以直接使用vs的debug。
在这里插入图片描述

// 打开后进入文件目录
D:\Program Files\Microsoft Visual Studio\2022\Community>cd D:\C++_resoure\Algotithm\test
// 查看内存结构代码:cl /d1 reportSingleClassLayout类名 "cpp文件名"
D:\C++_resoure\Algotithm\test>cl /d1 reportSingleClassLayoutBase "test.cpp"
//输出:
class Base      size(12):
        +---
 0      | base_1
 4      | base_2
 8      | base_3
        +---
// 和上面没有函数的对象的内存布局看起来没有变化,因为我们知道类的方法是放在代码区(四个分区:代码区,全局区,栈区,堆区)中,是所有类成员对象共享的,类对象的内存布局并不包含函数。

方法三:

class Base {
public:
    int base_1;
    int base_2;
    int base_3;
    virtual void func(){}
};
int main()
{
    Base base;
    return 0;
}

// 查看内存布局
class Base      size(24):
        +---
 0      | {vfptr}	// 这个就是常说的virtual function pointer 虚函数表的指针
 8      | base_1
12      | base_2
16      | base_3
        | <alignment member> (size=4)
        +---

Base::$vftable@:	
        | &Base_meta
        |  0
 0      | &Base::func
// 从上面我们看到了加上一个虚函数,就多出了一个vfptr的东西,大小为8字节,且从下面的Base::$vftable@:看到第0字节位置开始,记录了一个Base::func(Base的虚函数)的引用,也就是记录了函数的存放的地址。

这里结合最后一个工具解析这个对象的内存布局
在这里插入图片描述

后面的内容基本和下面的博客一样

https://blog.twofei.com/496/

  1. 注意指针的大小是不同的即可
  2. 后面还有一个不一样的地方就是定义了基类没有的虚函数的单继承的类对象布局的汇编代码那里,我的vs是用的16进制
class Base {
public:
    int base_1;
    int base_2;
    int base_3;
    
    virtual void Base_func_1() {}
    virtual void Base_func_2() {}
    virtual void Base_func_3() {}
};

class Son : public Base {
public:
    int son_1;
    int son_2;

    virtual void Base_func_1() {}
    virtual void Base_func_2() {}
    virtual void Son_func_1() {}
    virtual void Son_func_2() {}
};
int main()
{
    Son son;
    Son* son1 = &son;
    son1->Son_func_1();		// 断点
    son1->Son_func_2();		// 断点
    return 0;
}

两个断点的汇编代码

son1->Son_func_1();
00007FF7C2511C63  mov         rax,qword ptr [son1]  
00007FF7C2511C67  mov         rax,qword ptr [rax]  
00007FF7C2511C6A  mov         rcx,qword ptr [son1]  
00007FF7C2511C6E  call        qword ptr [rax+18h]  	// 18h是16进制,转化为10进制为24
    son1->Son_func_2();
00007FF7C2511C71  mov         rax,qword ptr [son1]  
00007FF7C2511C75  mov         rax,qword ptr [rax]  
00007FF7C2511C78  mov         rcx,qword ptr [son1]  
00007FF7C2511C7C  call        qword ptr [rax+20h]  	// 20h = 32

结合内存数据和对象的内存布局看
对象的内存布局:
在这里插入图片描述
__vfptr的内存数据:

// 使用小端的写法,低位的数据写在前面
// 前三个都可以看懂,分别是Son覆盖Base的两个方法和没有覆盖的一个Base的方法
0x00007FF788E4BBD8  e6 15 e4 88 f7 7f 00 00  
0x00007FF788E4BBE0  d2 15 e4 88 f7 7f 00 00  
0x00007FF788E4BBE8  dc 15 e4 88 f7 7f 00 00  
0x00007FF788E4BBF0  c3 15 e4 88 f7 7f 00 00  	// 结合上面看0x00007FF788E4BBD8 + 18h,说的就是从这开始18h = 24 = 3 * 8,Son自带的第一个虚函数
0x00007FF788E4BBF8  cd 15 e4 88 f7 7f 00 00  	// 20h = 32 = 4 * 8, Son的第二个自带的虚函数
0x00007FF788E4BC00  00 00 00 00 00 00 00 00  
0x00007FF788E4BC08  00 00 00 00 00 00 00 00  

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

这里也有些问题,他上面说了,这里展示以下结果

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() {}
};

int main()
{
    Derive1 d1;
    Derive1* pd1 = &d1;
    pd1->derive1_fun1();
    pd1->derive1_fun2();
    return 0;
}

断点出的汇编代码

pd1->derive1_fun1();
00007FF74AE81C1E  mov         rax,qword ptr [pd1]  
00007FF74AE81C22  mov         rax,qword ptr [rax]  
00007FF74AE81C25  mov         rcx,qword ptr [pd1]  
00007FF74AE81C29  call        qword ptr [rax+10h]  	// rax+10h = rax + 16 = 2 * 8,也就是第三项的开始位置
    pd1->derive1_fun2();
00007FF74AE81C2C  mov         rax,qword ptr [pd1]  
00007FF74AE81C30  mov         rax,qword ptr [rax]  
00007FF74AE81C33  mov         rcx,qword ptr [pd1]  
00007FF74AE81C37  call        qword ptr [rax+18h]  	// rax+18h = rax + 24 = 3 * 8, 也就是第四项开始位置

内存布局

在这里插入图片描述

内存数据

// 前三项不用说,base1的__vfptr 
0x00007FF74AE8BBF0  04 16 e8 4a f7 7f 00 00  
0x00007FF74AE8BBF8  1d 16 e8 4a f7 7f 00 00  
0x00007FF74AE8BC00  22 16 e8 4a f7 7f 00 00  	// 根据汇编得出子类的独有的virtual放在base1的后面,也就是后面两项
0x00007FF74AE8BC08  f5 15 e8 4a f7 7f 00 00  
0x00007FF74AE8BC10  08 d3 e8 4a f7 7f 00 00  	// 这个不懂是啥,大概是base2的一个象征
0x00007FF74AE8BC18  f0 15 e8 4a f7 7f 00 00  	// 后面两项是base2的两个虚函数,不太清楚,这里是不是说明一个对象中的父类共用一个虚函数表
0x00007FF74AE8BC20  ff 15 e8 4a f7 7f 00 00  
0x00007FF74AE8BC28  00 00 00 00 00 00 00 00  

// 这里还有一个问题,我做了一个测试,生成一个Base base对象,他的虚函数表也是再这个地址附近,不知道是不是在一个表上
0x00007FF65740BBB8  18 16 40 57 f6 7f 00 00  	// base
0x00007FF65740BBC0  1d 16 40 57 f6 7f 00 00  
0x00007FF65740BBC8  00 00 00 00 00 00 00 00  
0x00007FF65740BBD0  98 cf 40 57 f6 7f 00 00  	// 可能是子类的一个标识
0x00007FF65740BBD8  f0 15 40 57 f6 7f 00 00  	// base1
0x00007FF65740BBE0  13 16 40 57 f6 7f 00 00  
0x00007FF65740BBE8  70 d2 40 57 f6 7f 00 00  	// 子类的虚函数
0x00007FF65740BBF0  04 16 40 57 f6 7f 00 00  
0x00007FF65740BBF8  1d 16 40 57 f6 7f 00 00  
0x00007FF65740BC00  22 16 40 57 f6 7f 00 00  	// 可能是base2的标识
0x00007FF65740BC08  f5 15 40 57 f6 7f 00 00  
0x00007FF65740BC10  08 d3 40 57 f6 7f 00 00  
0x00007FF65740BC18  f0 15 40 57 f6 7f 00 00  	// base2的虚函数表
0x00007FF65740BC20  ff 15 40 57 f6 7f 00 00  
0x00007FF65740BC28  00 00 00 00 00 00 00 00 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值