继承、虚继承、虚函数内存分布(MSVC下)

继承、虚继承、虚函数内存分布(MSVC下),第1张

前提知识:

对象的内存中只包含成员变量,存储在栈区或堆区(使用 new 创建对象);

成员函数与对象内存分离,存储在代码区。


对象的大小,可以自己分析,int 四个字节,指针也是四个字节。


(在x86中)可用sizeof运算符查看对象的大小。


1、普通继承  代码示例
class A {
public:
	int ma;
	void func(){
		cout << "A :: func" << endl;
	}
};

class B : public A {
public:
	int mb;
	void func() {
		cout << "B :: func" << endl;
	}
};
内存分布
class A size(4):
        +---
 0      | ma
        +---

class B size(8):
        +---
 0      | +--- (base class A)
 0      | | ma
        | +---
 4      | mb
        +---

 2、普通多继承 代码示例

        派生类都只有一个基类,称为单继承。


C++ 也支持多继承,即一个派生类可以有两个或多个基类。


class A {
public:
	int ma;
	void func(){
		cout << "A :: func" << endl;
	}
};

class B : public A {
public:
	int mb;
	void func() {
		cout << "B :: func" << endl;
	}
};

class C : public A {
public:
	int mc;
	void func() {
		cout << "C :: func" << endl;
	}
};

class D : public B, public C {
public:
	int md;
	void func() {
		cout << "D :: func" << endl;
	}
};

 内存分布

        从多继承的内存分布中,我们可以看到存在一些缺点:在一个派生类中保留间接基类的多份同名成员;菱形继承中,会有命名冲突的出现。


        类 A 派生出类 B 和类 C,类 D 继承自类 B 和类 C,这个时候类 A 中的成员变量继承到类 D 中变成了两份,一份来自 A-->B-->D 这条路径,另一份来自 A-->C-->D 这条路径。


此时,在D中对A中的变量赋值,就会有命名冲突。


并且在使用基类A的公开成员函数时,也会指向不明确报错。


class A size(4):
        +---
 0      | ma
        +---

class B size(8):
        +---
 0      | +--- (base class A)
 0      | | ma
        | +---
 4      | mb
        +---

class C size(8):
        +---
 0      | +--- (base class A)
 0      | | ma
        | +---
 4      | mc
        +---

class D size(20):
        +---
 0      | +--- (base class B)
 0      | | +--- (base class A)
 0      | | | ma
        | | +---
 4      | | mb
        | +---
 8      | +--- (base class C)
 8      | | +--- (base class A)
 8      | | | ma
        | | +---
12      | | mc
        | +---
16      | md
        +---

 3、虚继承

        为了解决多继承时的命名冲突和冗余数据问题,C++ 提出了虚继承,使得在派生类中只保留一份间接基类的成员。


        在继承方式前面加上 virtual 关键字就是虚继承;

        虚继承的目的是让某个类做出声明,承诺愿意共享它的基类。


其中,这个被共享的基类就称为虚基类,本例中的 A 就是一个虚基类。


在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含一份虚基类的成员。


 代码示例
class A {
public:
	int ma;
	void func(){
		cout << "A :: func" << endl;
	}
};

class B :virtual public A {
public:
	int mb;
	void func() {
		cout << "B :: func" << endl;
	}
};

class C : virtual public A {
public:
	int mc;
	void func() {
		cout << "C :: func" << endl;
	}
};

class D : public B, public C {
public:
	int md;
	void func() {
		cout << "D :: func" << endl;
	}
};

 内存分布

在MSVC编译器中,引入虚基类表,如果某个派生类有一个或者多个虚基类,编译器会在派生类对象中安插一个指针(vbptr)指向虚基类表(vbtable)。


虚基类表放的是偏移量。


class A size(4):
        +---
 0      | ma
        +---



class B size(12):
        +---
 0      | {vbptr}
 4      | mb
        +---
        +--- (virtual base A)
 8      | ma
        +---

B::$vbtable@:
 0      | 0
 1      | 8 (Bd(B+0)A)



class C size(12):
        +---
 0      | {vbptr}
 4      | mc
        +---
        +--- (virtual base A)
 8      | ma
        +---

C::$vbtable@:
 0      | 0
 1      | 8 (Cd(C+0)A)



class D size(24):
        +---
 0      | +--- (base class B)
 0      | | {vbptr}
 4      | | mb
        | +---
 8      | +--- (base class C)
 8      | | {vbptr}
12      | | mc
        | +---
16      | md
        +---
        +--- (virtual base A)
20      | ma
        +---

D::$vbtable@B@:
 0      | 0
 1      | 20 (Dd(B+0)A)

D::$vbtable@C@:
 0      | 0
 1      | 12 (Dd(C+0)A)

 4、虚函数

        如果一个类包含了虚函数,那么在创建该类的对象时就会额外地增加一个数组,数组中的每一个元素都是虚函数的入口地址。


每定义的任何一个对象,都会有vfptr,vfptr指向的是同一块vftable。


        子类继承了父类之后,同名函数(包括返回值,函数名,参数列表)也会自动是virtual类型。


子类的vftable中的函数,只有 virtual类型,不是virtual类型的,不会在vftable中。


代码示例
class A {
public:
	int ma;
	virtual void func() {
		cout << "A" << endl;
	}
};
class B : public A {
public:
	int mb;
	void func() {
		cout << "B" << endl;
	}
	void func2() {
		cout << "B" << endl;
	}
	virtual void func3() {

	}
};
内存分布
class A size(8):
        +---
 0      | {vfptr}
 4      | ma
        +---

A::$vftable@:
        | &A_meta
        |  0
 0      | &A::func


class B size(12):
        +---
 0      | +--- (base class A)
 0      | | {vfptr}
 4      | | ma
        | +---
 8      | mb
        +---

B::$vftable@:
        | &B_meta
        |  0
 0      | &B::func
 1      | &B::func3

5、虚继承(多)、虚函数 代码示例
class A {
public:
	int ma;
	virtual void func(){
		cout << "A :: func" << endl;
	}
};

class B :virtual public A {
public:
	int mb;
	void func() {
		cout << "B :: func" << endl;
	}
};

class C : virtual public A {
public:
	int mc;
	void func() {
		cout << "C :: func" << endl;
	}
};

class D : public B, public C {
public:
	int md;
	void func() {
		cout << "D :: func" << endl;
	}
};
 内存分布
class A size(8):
        +---
 0      | {vfptr}
 4      | ma
        +---

A::$vftable@:
        | &A_meta
        |  0
 0      | &A::func


class B size(16):
        +---
 0      | {vbptr}
 4      | mb
        +---
        +--- (virtual base A)
 8      | {vfptr}
12      | ma
        +---

B::$vbtable@:
 0      | 0
 1      | 8 (Bd(B+0)A)

B::$vftable@:
        | -8
 0      | &B::func

class C size(16):
        +---
 0      | {vbptr}
 4      | mc
        +---
        +--- (virtual base A)
 8      | {vfptr}
12      | ma
        +---

C::$vbtable@:
 0      | 0
 1      | 8 (Cd(C+0)A)

C::$vftable@:
        | -8
 0      | &C::func


class D size(28):
        +---
 0      | +--- (base class B)
 0      | | {vbptr}
 4      | | mb
        | +---
 8      | +--- (base class C)
 8      | | {vbptr}
12      | | mc
        | +---
16      | md
        +---
        +--- (virtual base A)
20      | {vfptr}
24      | ma
        +---

D::$vbtable@B@:
 0      | 0
 1      | 20 (Dd(B+0)A)

D::$vbtable@C@:
 0      | 0
 1      | 12 (Dd(C+0)A)

D::$vftable@:
        | -20
 0      | &D::func

 6、多态的指向

在多态中,基类指针指向派生类对象时候,永远指向的是派生类基类部分数据的起始地址。


父类并不能访问子类中在父类未出现的函数和变量。


只能访问虚函数表中的函数。


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/563482.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-02
下一篇 2022-04-02

发表评论

登录后才能评论

评论列表(0条)

保存