C++虚函数表、虚表指针、对象内存分布

一、没有虚函数的对象,它的内存分布。

#include <stdio.h>
#include <stddef.h>		// 定义了offsetof宏

class Base {
public:
	int m_val1;
	int m_val2;

	void fun() {}
};

int main()
{
	Base b;
	int offset1 = offsetof(Base, m_val1);
	int offset2 = offsetof(Base, m_val2);
	b.fun();
	return 0;
}


对象大小及偏移:

sizeof(Base)8
offsetof(Base, m_val1)0
offsetof(Base, m_val2)4

如图:

在这里插入图片描述

  1. 成员变量是按照定义的顺序来保存的,最先声明的在最上边,然后依次保存,类对象的大小就是所有成员变量大小之和。
  2. 如果对象没有虚函数,那么它就不可能会发生动态绑定,也就不会对对象的内存布局造成任何影响。


二、拥有一个虚函数的类对象。

class Base {
public:
    int m_val1;
    int m_val2;

    virtual void fun() {}
};


类对象中多了个虚函数表指针

在这里插入图片描述


__vfptr的定义伪代码大概如下:

//	上图中可看出,虚函数表中会多存放一个元素
void*   __fun[2] = { &Base::b_fun };
const void**  __vfptr = &__fun[0];


对象大小及偏移:

sizeof(Base)12
offsetof(Base, m_val1)4
offsetof(Base, m_val2)8

  1. __vfptr被定义成一个指向指针数组的指针,数组的长度是虚函数个数+1
  2. __vfptr前面有const修饰,它修饰的是那个虚函数表。


三、拥有多个虚函数的类对象。

class Base {
public:
	int m_val1;
	int m_val2;

	virtual void fun1() {}
	virtual void fun2() {}
};


虚函数表中增加了第2个元素, 其值为第2个虚函数fun2()的函数地址:

在这里插入图片描述

对象大小及偏移:

sizeof(Base)12
offsetof(Base, m_val1)4
offsetof(Base, m_val2)8

增加一个虚函数, 只是简单地向该类对应的虚函数表中增加一项而已, 并不会影响到类对象的大小以及布局情况。


四、同一个类的不同实例,共用同一份虚函数表。

#include <stdio.h>
#include <stddef.h>		// 定义了offsetof宏

class Base {
public:
	int m_val1;
	int m_val2;

	virtual void fun1() {}
	virtual void fun2() {}
};

int main()
{
	Base b1;
	Base b2;
	int offset1 = offsetof(Base, m_val1);
	int offset2 = offsetof(Base, m_val2);
	b1.fun1();
	return 0;
}


两个对象的虚函数成员如下:

在这里插入图片描述

同一个类的不同实例共用同一份虚函数表,它们都通过一个虚函数表指针__vfptr指向该虚函数表。

在这里插入图片描述


五、基类定义了虚函数,派生类没有新增虚函数。

class Base {
public:
	int m_val1;
	int m_val2;

	virtual void fun1() {}
	virtual void fun2() {}
};

class Driver : public Base {
public:
	int m_val3;
	int m_val4;
};


派生类对象内存分布:

在这里插入图片描述

基类在上边, 继承类的成员在下边依次定义。


六、派生类覆盖基类虚函数。

#include <stdio.h>
#include <stddef.h>		// 定义了offsetof宏

class Base {
public:
	int m_val1;
	int m_val2;

	virtual void fun1() {}
	virtual void fun2() {}
};

class Driver : public Base {
public:
	int m_val3;
	int m_val4;

	virtual void fun1() {}
};

int main()
{
	Driver d;
	d.fun1();
	return 0;
}


派生类对象的func1()地址发生改变,指向新的实现地址:

在这里插入图片描述

通过基类指针指向派生类对象时,调用的是继承后被覆盖的函数,这就是多态。

在这里插入图片描述


七、派生类新增虚函数。

#include <stdio.h>
#include <stddef.h>		// 定义了offsetof宏

class Base {
public:
	int m_val1;
	int m_val2;

	virtual void fun1() {}
	virtual void fun2() {}
};

class Driver : public Base {
public:
	int m_val3;
	int m_val4;

	virtual void fun3() {}
};


int main()
{
	Driver d;
	int offset1 = offsetof(Driver, m_val3);
	int offset2 = offsetof(Driver, m_val4);
	d.fun1();
	return 0;
}


对象大小及偏移:

sizeof(Driver)20
offsetof(Driver, m_val3)12
offsetof(Driver, m_val4)16


__vfptr数组大小成变了4,新增了派生类的虚函数地址,但是Base部分只展示基类的虚函数,所以没有窗口中展示。

在这里插入图片描述

派生类新增的虚函数被加在基类虚函数表的后面!!

在这里插入图片描述


八、如果发生多继承,派生类中新增的虚函数应该加在哪?覆盖呢?

#include <stdio.h>
#include <stddef.h>		// 定义了offsetof宏

class BaseA {
public:
	int m_val1;
	int m_val2;

	virtual void fun1() {}
	virtual void fun2() {}
};

class BaseB {
public:
	int m_val3;
	int m_val4;

	virtual void fun3() {}
	virtual void fun4() {}
};

class Driver: public BaseA, public BaseB {
public:
	virtual void fun1() {}		// 覆盖BaseA::func1()
	virtual void fun3() {}		// 覆盖BaseB::func3()
	virtual void fun5() {}		// 新增virtual func5()
};

int main()
{
	Driver d;
	d.fun1();
	return 0;
}


派生类内存布局:

在这里插入图片描述

1. 覆盖了哪个基类,就修改对应的虚函数表。
2. 新增的虚函数加在第一个虚函数表之后。
3. 如果基类中都没有虚函数,而派生类中有,则派生类对象中同样存在虚函数表,并且是在最前面。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值