资讯详情

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内存对象第四~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 

标签: 24bc08集成电路ic

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台