c/c++ 虚函数
2021-01-24 13:17
标签:抽象类 多重继承 oid mem 机器 cout 通过 details 无法创建 1.虚函数:根据基类指针指向的对象的不同,调用不同类的方法 提供多态,根据基类指针指向的对象的不同,调用不同类的方法 纯虚函数通常用于基类 , 用来提供一种类的接口规范,是一种声明,但是没有具体实现,含有纯虚函数的类一定是抽象类(abstract class), 抽象类无法创建对象, 如下,将2.1的例子稍微改一下 参照ref2中的回答,c++中的对象的成员函数并非通过在对象中放置函数指针实现的,而是编译的时候将该对象的指针传入函数中进行调用,而对于虚函数,指针的多态使用,使得无法在编译期确定实际的类型,就没法找到对应的函数将对象指针传入;为此,便在每个对象内存的头部存放了一个虚表指针,该虚表中存放着实现的虚函数地址,使用这些地址进行调用即可 将以上内容写入main.cpp之后编译, 使用gdb在return 0处打断点 对于derive对象,可以看到其虚表指针_vptr.base的值为0x400ae0, 处于derive虚表偏移16的位置,减去16个字节,打印完整的derive的虚表如下,(注:此处为64位机器,故指针为8字节) 可以看到0x400ae0 <_ztv6derive>中的存储的函数地址为0x00400990(x86默认小端,所以倒着读),以及0x400ae8 <_ztv6derive>的存储的函数地址为0x004009ae,查看一下这两个地址, 指向了derive:fn1()的函数di‘zhi 虚函数表的索引机制如下图,此外,关于虚表的第一项和第二项,虚表第一项<_ztv6derive>是0,用来做分割,因派生类的虚表和基类的虚表在内存上是连续的; 第二项<_ztv6derive>, 指向的是一个type info信息,这提供RAII中的实现用到的东西,typeinfo以及dynamic_cast会用到此消息 使用gdb调试一段多态汇编代码,对应上面的索引方式 多重继承下,几成继承对象就会有几个虚表指针,子类实现的虚函数会覆盖所有多重继承中的同名虚函数,子类新添加的虚函数会加在第一个虚函数表之后,一个例子: derive2 虽然继承derive,所以直接继承其虚表的结构,因为derive中的虚函数表是多重继承得来的,其有两个虚表指针,所以derive2中也有两个,但是derive2中对fn1()和fn2()进行了重写,所以其拷贝了derive之后,对涉及到这两个函数地址的条项都进行了覆盖,并且derive2新增了virtual void fn3(),这一项会新增到第一个大表或小下的末尾或,即 1.使用gdb调试虚函数表 c/c++ 虚函数 标签:抽象类 多重继承 oid mem 机器 cout 通过 details 无法创建 原文地址:https://www.cnblogs.com/ishen/p/12844965.html1.概览
2.纯虚函数用来提供接口规范,而不必实现一个纯虚函数提出的方便,只是一个声明而不是定义,所以没法创建一个抽象类
4.虚函数是通过在类内存放虚函数指针,其指向虚函数表来实现的
5.子类虚函数表的初始化是拷贝父类虚函数表,子类实现的同名的虚函数就用子类的虚函数的地址去覆盖,所以继承的虚函数不实现时,调用的最邻近基类的虚函数
6.多重继承下,几重继承就会有几个虚函数表指针,派生类新增的基函数会新增到派生类的第一个虚函数表末尾2.正文
2.1.虚函数
public base{
public:
virtual void fn(){
coutfn(); //base
*ptr = &d;
ptr->fn(); //derive
return 0;
}
2.2 纯虚函数
public base{
public:
virtual void fn()= 0; //纯虚函数,在此只是声明,而不想定义,提供的只是一种接口规范
};
public derive: public base{
public:
virtual void fn(){
coutfn(); //derive
return 0;
}
2.3 虚函数表
一个简单的例子,加以说明#include
//打印对象虚表
(gdb) set print object on
(gdb) set print pretty on
//打印base对象
(gdb) p b
$1 = (base) {
_vptr.base = 0x400b00
(gdb) x/32xb 0x400ad0
0x400ad0 <_ztv6derive>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x400ad8 <_ztv6derive>: 0x10 0x0b 0x40 0x00 0x00 0x00 0x00 0x00
0x400ae0 <_ztv6derive>: 0x90 0x09 0x40 0x00 0x00 0x00 0x00 0x00
0x400ae8 <_ztv6derive>: 0xae 0x09 0x40 0x00 0x00 0x00 0x00 0x00
(gdb) x/i 0x00400990
0x400990 <:fn1>: push %rbp
(gdb) x/i 0x004009ae
0x4009ae <:fn2>: push %rbp
32 derive d;
0x00000000004008cd : lea rax,[rbp-0x20]
0x00000000004008d1 : mov rdi,rax
0x00000000004008d4 : call 0x4009f6 <:derive>
33 base *ptr = &d;
0x00000000004008d9 : lea rax,[rbp-0x20]
0x00000000004008dd : mov QWORD PTR [rbp-0x28],rax
34 ptr->fn2();
0x00000000004008e1 : mov rax,QWORD PTR [rbp-0x28] //将&d的指针位置放进rax寄存器,其直接指向虚表第三个元素,derive:fn1()所在的位置
0x00000000004008e5 : mov rax,QWORD PTR [rax] //到虚表第三个元素derive::fn1()
=> 0x00000000004008e8 : add rax,0x8 //偏移,到第四个元素,函数derive::fn2()
0x00000000004008ec : mov rax,QWORD PTR [rax] //到derive::fn2()
0x00000000004008ef : mov rdx,QWORD PTR [rbp-0x28]
0x00000000004008f3 : mov rdi,rdx
0x00000000004008f6 : call rax //调用derive::fn2()
2.4 多继承下的虚表
class base{
public:
virtual void fn1(){
cout
$2 = (derive2) {
ref
2.知乎: c++为什么要引入虚表,果冻虾仁的回答
3.继承中虚表的内存布局