c++对象内存布局

vfptr:虚函数表指针, vbptr:虚基表指针

  1. 基础知识

内存分为:全局静态存储区,代码区,常量存储区,堆,栈。其中全局静态存储区存放全局变量和所有静态变量(类成员和外部定义的静态变量)。一切函数实现存放在代码区,栈存放函数体内部的局部变量,函数参数,返回值。

成员变量:

静态成员变量存在全局静态存储区,不占用对象内存。
非静态成员变量存储于对象内存,还要加上对齐的字节。

成员函数:

非静态成员函数:函数参数列表中有默认的指向对象指针的参数,调用时会把this指针传入,所以在函数内部可以访问对象的成员变量和函数。

静态成员函数:没有this指针参数,所以静态函数内部只能调用类的静态成员变量以及静态成员函数。

虚函数:函数实现依旧存于代码区,但是函数地址存在于虚函数表(存放于全局静态存储区)中,对象中会存在虚表指针指向虚函数表。
综上:对象内存布局为:虚函数表(或无)–>非静态成员变量–>对齐字节,表示存储位置的顺序。这里的虚函数表实际上是虚表指针,只有虚表指针存在内存中,以下同理。

而对于非虚函数,编译器在编译时期会把调用函数的地方直接换成函数地址。如:

class B
{
public:
	void f() {  cout << "B::f" << endl; }
}
int main()
{
	B* b = NULL;
	b->f(); //B::f 在编译器这段代码被换成f()函数在代码区的地址。
	
	return 0;
}

编译器查找名称是由内而外的,先查找子类没有该函数定义就查找基类,如果是多继承,而且多个基类恰好有同名函数定义,那么调用就会出错,可以通过指定基类作用域的方式来调用。

class A {
public:
	 void func()
	{
		cout << "a";
	}
};

class A1 {
public:
	void func()
	{
		cout << "a1";
	}
};

class B :public A, public A1 {

};

int main(int argc, char** argv)
{
	B b;
	b.func();//报错
	b.A::func();
	b.A1::func();

	system("pause");
}
  1. 继承关系下虚函数表分布 以及对象内存分布

对于单继承来说对象内存分布为:基类虚函数表(或无)–>基类成员变量–>派生类成员变量–>对齐字节。

继承之后如果基类有虚函数表,那么子类的虚函数地址就保存在基类虚函数表中,子类中不单独设计虚函数表。

(1)下面看多继承的例子。

class B
{
public:
	virtual void f() {cout << "B::f" << endl; }
	virtual void g1(){cout<< "B::g""<<endl;}
	int a;
};

class C {
public:
	virtual void f() { cout << "C::f" << endl; }
	virtual void g2(){cout<< "C::g""<<endl;}
	int a;
};

class D :public B, public C
{
public:
	void f() { cout << "D::f" << endl; }
	virtual void g(){cout<< "D::g""<<endl;}
};

D d;
d.a;//访问存在二义性,是B还是C的a。只能用作用域运算符显性指定。d.B::a;相当于d对象中的A部分。

子类对象中内存为:虚函数表1->基类1成员变量->虚函数表2->基类2成员变量->子类的成员变量。也就是说子类中有两个虚表指针分别指向两个父类的虚函数表。对于子类单独定义的虚函数会保存在第一个虚函数表中,这里的虚函数表存储的先后位置是在类声明时决定的class D: public B, public C所以第一个虚函数表是B类的虚函数表。

如果在D类中没有覆盖f()方法的话,虚函数表如下
D::vftable@B

&B::f
&B::g1
&D::g

D::vftable@C

&C::f
&C::g2

当子类中覆盖了f()方法时,虚函数表:
D::vftable@B

&D::f
&B::g1
&D::g

D::vftable@C

&D::f
&C::g2

也就是会用子类的函数地址替换掉所有重名父类的函数地址,这也是实现多态的关键。这样的话父类指针用子类对象实例化后调用虚函数,就只能调用子类中的实现,因为在子类对象的虚函数表中父类虚函数地址都被覆盖掉了。

如果两个基类中存在同名的成员函数或同名成员变量,而且派生类没有重新覆盖,那么派生类对象在访问基类的同名成员函数/变量时就存在二义性,到底调用哪个基类呢?当然覆盖的话就不会出现了。

(2)菱形继承
B,C类均继承A类,而Dlei多继承于B,C就形成菱形继承。

class A {
public:
	virtual void f() { cout << "A::f" << endl; }
	int a;
};

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

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

};

class D :public B, public C
{
public:
	virtual void f() {
		cout << "D::f" << endl;
	}
};

int main()
{
	D d;
	d.f();
	
	return 0;
}

内存分布: A虚表–>A成员变量–>B成员变量 -->A虚表–>A成员变量–>C成员变量 -->D成员变量
在这里插入图片描述
所以不管继承多少层都只保存最顶端基类的虚函数表,但多路继承时每路对象都保存一份基类对象内存,造成重复存储,这是多路继承的问题,单路继承就没有这个问题。

同时菱形继承也具备上述二义性。

(3)解决方案-- 虚继承

在继承方式前加上virtual表示虚继承,每个虚继承的派生类都会有一个虚基表指针vbptr,指向虚基表,虚基表中存放的是基类成员变量的相对内存地址。也就是把原来存放基类变量的地方换成了vbptr。

虚继承时基类的内存存放在派生类内存之后。所以分析多层继承关系时先分析一般继承的内存:基类内存在对象开始位置;分析虚继承:积累内存在对象内存结束位置,多层虚继承时对象内存从顶向下分析。

class Base
{
public:
    Base (int a = 1):base(a){}
    void fun0(){cout << base << endl;}
    int base;
};
class Base1: virtual public Base
{
public:
    Base1 (int a = 2):base1(a){}
    void fun1(){cout << base1 << endl;}
    int base1;
};
class Base2:virtual  public Base
{
public:
    Base2 (int a = 3):base2(a){}
    void fun2(){cout << base2 << endl;}
    int base2;
};
class Derive: virtual  public Base1, virtual  public Base2
{
public:
    Derive (int value = 4):derive (value){}
    void fun3(){cout << derive << endl;}
    int derive;
};

在这里插入图片描述
如果D一般继承B,C那么
在这里插入图片描述
参考:https://www.cnblogs.com/longcnblogs/archive/2017/10/09/7642951.html

  1. 访问权限的继承

因为基类的private成员变量和成员函数只能在当前类中使用,其派生类无法使用,所以只讨论基类public,protected成员在继承后的访问权限。使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。

public继承后,基类成员的访问权限不变。
protected继承,成员访问均变为protected。基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected.可 以看出保护成员限定符是因继承才出现的 。
private继承,成员访问均变为private。

继承方式基类public成员基类protected成员基类private成员
public继承不变不变不可见
protected继承protectedprotected不可见
private继承privateprivate不可见

静态成员的继承:
因为静态成员变量存放于静态存储区,所以派生类可以继承但要受到上述访问权限的约束,而且派生类和基类的成员变量共用同一块内存,这在使用静态成员做引用计数时要注意。一般类中用于引用计数的静态成员要设置为private,以防派生类对其进行修改。
静态成员函数同理,派生类和基类的成员函数地址相同,shou访问权限约束。

class A {
public:
	static void func()
	{
		cout << "a";
	}
};
class B :public A {
};

int main(int argc, char** argv)
{
	B b;
	cout << B::func << endl;
	cout << A::func << endl;//两者地址相同。注意只有非静态成员函数取地址加&。
	system("pause");
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值