浅析GCC下C++类对象的内存布局

继承是C++作为OOD程序设计语言的三大特征(封装, 继承和多态)之一, 单一非多态继承是比较好理解的, 本文主要讲解GCC环境下的多重继承和虚拟继承的对象内存布局.

一,多重继承

先看几个类的定义:

class Top
{
public:
	int a;
};

class Left : public Top
{
public:
	int b;
};

class Right : public Top
{
public:
	int c;
};

class Bottom : public Left, public Right
{
public:
	int d;
};

不难想象, Left和Right类的内存布局如下图所示:



我们如下进行验证:

Left *left = new Left();
Top *top = left;
cout << left <<  '\t' << top << endl;//输出:0x902c008       0x902c008
Right *right = new Right();
top = right;
cout << right << '\t' << top << endl;//输出:0x902c018       0x902c018


从输出结果可以看出, 父类指针top指向子类对象left和right的起始地址, 与上述内存布局吻合.
在非虚拟多重继承的情况下, 子类的内存布局是什么样子的呢? 如下所示:


可以看出, Bottom类由于继承了Left和Right, 而Left和Right又分别继承了Top. 因此, Bottom包含了Top两次!
下面进行验证:

Bottom *bottom = new Bottom();   
top = (Left *)bottom;
left = bottom;
cout << bottom << '\t' << top << '\t' << left << endl;//输出:0x9930028 0x9930028 0x9930028
top = (Right *)bottom;
right = bottom;
cout << bottom << '\t' << top << '\t' << right << endl;//输出:0x9930028 0x9930030 0x9930030

从输出结果可以看出, left指针和right指针分别指向了bottom对象中它们所处的位置:


由于bottom对象中存在两部分top对象, 因此不能直接用top指针指向bottom对象, 因为编译器不知道你的意图到底是指向left中的bottom部分, 还是right中的bottom部分. 需要进行转换才可以. 如果需要通过bottom指针分别访问left和right中的top部分,可以如下:  bottom->Left::a, bottom->Right::a.
好了, 到这里讲完了非虚拟继承下的多重继承的内存布局情况, 相信大家应该有一个比较清晰的认识了. 最重要的一点是: 多重继承时, 父类共同继承的祖父类会在子类中有多份存在.


二,虚拟继承
平时讨论的最多的是虚函数, 很少涉及到虚拟继承的情况. 那么, 虚拟继承到底是一个什么概念呢?
先来看一个例子:

#include <iostream>
using namespace std;

class Father
{
public:
	int a;
};

class Child : virtual public Father
{
public:
	int b;
};

int main()
{
	cout << sizeof(Father) << '\t' << sizeof(Child) << endl;//输出:4   12
	Child child;
	cout << &child << '\t' << &child.b << '\t' << &child.a << endl;//输出:0xbfc08124 0xbfc08128 0xbfc0812c
	return 0;
}

对, 你没有看错, 类的大小输出不是4   8, 而是4   12. 虚拟继承时, 编译器会在子类中安插上一个虚表指针.

从输出的对象成员地址来看, 我们可以得到Child类的如下内存布局:


现在我们对多重继承的例子进行改造:

class Top
{
public:
	int a;
};

class Left : virtual public Top
{
public:
	int b;
};

class Right : virtual public Top
{
public:
	int c;
};

class Bottom : public Left, public Right
{
public:
	int d;
};

把Left和Right改成了虚拟继承Top.
从上面验证简单虚拟继承时, 编译器安插虚表指针的例子, 我们可以想象出此时Bottom类的对象内存布局如下:


对, 你没有看错! 虚拟继承时, 子类只有父类共同继承的祖父类的一份存在. 这其实也就是虚拟继承的最大用途. 此时, Top, Left, Right和Bottom对象的大小分别为:4, 12, 12, 24.既然有虚表指针了, 那么Bottom的虚表是什么样的呢? 请看:


有了虚表, 内存布局情况一目了然. 下面我们进行验证:

Bottom *bottom = new Bottom();
top = bottom;
cout << bottom << '\t' << top << endl;//输出:0x9fa5028       0x9fa503c
Left *left = bottom;
cout << bottom << '\t' << left << endl;//输出:0x9fa5028       0x9fa5028
Right *right = bottom;
cout << bottom << '\t' << right << endl;//输出:0x9fa5028       0x9fa5030

根据输出结果, 我们可以知道指针的指向情况:


由于引入了虚指针和虚表, left指针和right指针可以根据虚表提供的偏移量信息, 轻松访问到Top::a.
到此为止, 已经讨论清楚了多重继承和虚拟继承下的对象内存布局情况. 总结下: 非虚拟多重继承时, 子类会有父类共同继承祖父类的多份存在; 虚拟继承时, 子类会被安插一个虚拟指针; 多重虚拟继承时, 子类只有父类共同继承祖父类的一份存在. 通过父类的虚拟指针, 可以正确地访问祖父类中的成员.

另外, static 成员不属于对象, 而是该类所有对象共有.


转载自: http://my.oschina.net/pathenon/blog/66784

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值