深度探索c++对象模型-23-多继承第二基类虚函数支持、虚继承带虚函数

先看看单继承下,不加虚析构函数的情况:

#include<iostream>
using namespace  std;
class basic
{
public:
	virtual void go()
	{
		cout << "basic::go\n";
	}
	~basic(){ cout << "~basic()\n"; }
};
class Child :public basic
{
public:
	virtual void go()
	{
		cout << "Child::go\n";
	}
	~Child(){ cout << "~Child()\n"; }
};
void main()
{
	{
		basic *c = new Child;
		delete c;
	}
	system("pause");
}

结果:
在这里插入图片描述
给父类base析构函数加virtual,如下:

virtual ~basic(){ cout << "~basic()\n"; }

结果:
在这里插入图片描述

一、多重继承第二基类对虚函数支持的影响(this指针调整作用)

子类继承了几个父类,子类就有几个虚函数表
this指针调整的目的就是让对象指针正确的指向对象的首地址,从而能正确的调用对象的成员函数或者说正确确定数据成员的存储位置。

看如下代码:

class Base
{
public:

	Base()
	{
		printf("Base::this = %p\n",this);
	}
	virtual void f()
	{
		cout << "Base:f()" << endl;
	}

	virtual void g()
	{
		cout << "Base:g()" << endl;
	}


	virtual void h()
	{
		cout << "Base:h()" << endl;
	}

	virtual ~Base()
	{
		cout << "Base::~Base()" << endl;
	}
};

class Base2
{
public:
	virtual void hBase2()
	{
		cout << "Base2:hBase2" << endl;
	}

	virtual ~Base2()
	{
		cout << "Base2::~Base2()" << endl;
	}
};


class Derive :public Base, public Base2
{
public:

	Derive()
	{
		printf("Derive::this = %p\n", this);
	}

	virtual void g()
	{
		cout << "Derive:g()" << endl;
	}

	virtual void i()
	{
		cout << "Derive:g()" << endl;
	}
	~Derive()
	{
		cout << "Derive::~Derive()" << endl;
	}
};

1.通过指向第二个 基类的指针调用继承类的虚函数时

Base2 *pb2 = new Derive;	
delete pb2;

上一节已经说过了,pb2指向Derive对象中的第二基类(Base2),所以为了能够正确执行derive的析构函数,pb2必须要调整指向Derive对象首地址(减掉4)。

2.一个指向派生类的指针调用第二基类中的虚函数时

Derive *pd= new Derive;
pd->hBase2();
delete pd;

上述代码hBase2()是第二基类中函数,要想成功调用,必须对this指针进行调整,让this指针指向Base2的类子对象的首地址。
看下面反汇编代码:
在这里插入图片描述
其中有一句 add ecx,4就是说this指向向下移动4个字节(+4)。这也及时Base2的首地址。

在这里插入图片描述
3.允许虚函数的返回值类型有所变化时
分别在Base,Base2,Dervie中增加如下的代码:

virtual Base *clone() const
	{
		return new Base();
	}
virtual Base2 *clone() const
	{
		return new Base2();
	}
virtual Derive *clone() const
	{
		return new Derive();
	}
void main()
{

	Base2 *pb1 = new Derive;
	Base2 *pb2 = pb1->clone();

	system("pause");
}

其中

pb1->clone();//将执行Derive::clone()

因为它是虚函数,动态绑定。执行时,pb1首先会将this指针调整到Derive对象的开始地址,这样调用的才是Derive的clone(),所以this指针应该减小。

在这里插入图片描述
可以看到有两个thunk,转到反汇编:
在这里插入图片描述
注意那个+8,其实也就是调用虚函数表中[2](也就是第二个thunk),继续执行到call
在这里插入图片描述
在这里插入图片描述
注意sub ecx,4这就意味着this指针往回走了4个字节,正好走到了真实的Derive对象的首地址,然后jmp跳转到Derive::clone().

执行完Derive::clone()后返回的是Derive对象的指针,但是是用Base2 *去接的,这样就还需要进行一个this指针调整,也就是往后走4个字节(+4),让其指向Base2对象的首地址。
二、虚继承下的虚函数
看如下代码:

class Base
{
public:

	int m_nbase1;
	virtual void f()
	{
		cout << "Base:f()" << endl;
	}

	
	virtual ~Base()
	{
		cout << "Base::~Base()" << endl;
	}


};

class Derive :public virtual Base
{
public:

	int m_nderive;

	~Derive()
	{
		cout << "Derive::~Derive()" << endl;
	}


};


void main()
{

	cout << "sizeof(Derive) = " << sizeof(Derive) << endl;
	Derive dobj;
	dobj.m_nbase1 = 2;
	dobj.m_nderive = 5;

	Derive *pdobj = new Derive;
	pdobj->f();

	system("pause");
}

加个断点,位置如下:
在这里插入图片描述
切换到内存,如下:
因为是16个字节,所以我们只看16个字节,如下:
在这里插入图片描述
从这个布局我们可以推算出如下的布局:
在这里插入图片描述
其实上面连个问号,一个是虚函数表指针,一个是虚基类表指针,只是不知道哪个在哪个位置,接下来就是如何确定他们的位置。

接下来这么调用:

Derive *pdobj = new Derive;
pdobj->m_nbase1 = 2;
pdobj->m_nderive = 5;
pdobj->f();

由前面的内容,已经知道了第,第2,第4格的内容分别是5,2。那虚函数表的地址,不是第1格,就在第3格子,假设在第3格,

下面是pdobj的内存地址:
在这里插入图片描述
下面是pdobj的内存,我们查看它第三格的地址,也就是如下的地址:0x002becbc (和显示的反着),接下来去这个地址看看:
在这里插入图片描述
反过来就是002b1398

看看调用f()的汇编代码:
在这里插入图片描述

下面是寄存器的地址
在这里插入图片描述
其实eax中的值也就是f()的地址。如下证明:
在这里插入图片描述

这和第三格里面的内容一致,所以虚函数表指针在第三格,所以布局如下:
在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

发如雪-ty

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值