c++类的内存结构(单继承)

本文详细探讨了C++中单继承类的内存结构,包括不带虚函数、带虚函数、带虚析构、纯虚函数等不同情况。讨论了虚函数表(__vfptr)、虚析构函数、派生类的内存布局以及多态性的工作原理,强调了基类虚析构函数的重要性,并提供了测试代码以验证理论。
摘要由CSDN通过智能技术生成

关于c++类的内存的文章在网上有许多,但是,大多是一段段代码和说明,偶尔有图文并茂的,总觉不是很详细,没有一目了然之感觉

并且大部分也没有涉及虚析构,也没有说明访问标号的影响(虽然事实是没有影响)。而这块内容对于理解多态、重写、继承的构造和析构又是足够重要的。

终于下定决心,自己动手,在参考多方资料后,终有此文。

一、基类内存结构

1.1 不带虚函数、虚析构的基类:

class Base
{
public:	
	Base():i(3),j(4){}

	int i;

private:
	int j;
};
这是最简单的情况,直接给图:

数据成员的排列将基于声明顺序,尽管数据成员j是私有的,这并没有什么影响。

1.2 不带虚函数、带虚析构的基类:

class Base
{
public:	
	Base():i(3),j(4){}
	virtual ~Base(){}

	int i;

private:
	int j;
};
稍复杂一点,像是这样:


多了一个虚函数表__vfptr[],它可以看做是一个指向函数的指针数组(void**),其成员都是指向函数的指针(void*),更确切的说,都指向此类的虚函数,除了最后一个结束标记(值为0,这里编译器是vs,其他编译器可能有所不同),大小就是此类中含虚函数的个数(含继承的)+1(结束标记),测试程序将在后面给出。

图中&bObj是Base类的一个对象bObj的地址,图中可以看到它的值是虚表的地址,那么&bObj的类型(void***),是指向虚表__vfptr的指针。

因为我们显式声明了虚析构,所以虚表的大小是2,但是__vfptr[0]并非像预想中的直接指向~Base();而是指向“析构代理函数”。

所谓析构代理函数,是编译器替我们生成的虚成员函数,它会动态的调用类的对应析构函数,并做析构相关的额外一些其他事情。

其类似实现原理参看这里:http://blog.csdn.net/jiangdf/article/details/8917020

1.3 带虚函数、不带虚析构的基类:

class Base
{
public:	
	Base():i(3),j(4){}
 	virtual void f() { std::cout << "Base::f" << std::endl; }
 	virtual void g() { std::cout << "Base::g" << std::endl; }
	int i;

private:
	virtual void h() { std::cout << "Base::h" << std::endl; }
	int j;
};
这个基类有些怪异,含一个私有的虚函数,这比较少见,虽然他将被派生类继承,但不能被直接访问。更重要的是它没有虚析构函数,这是一种 错误,为了全面起见,

罗列在此。

内存图将是这样的:

也是按照声明的顺序,虚函数表依次指向各虚函数,虽然h是私有的,这也没什么影响

1.4 含虚函数与虚析构的基类

class Base
{
public:	
	Base():i(3),j(4){}
	virtual ~Base(){}
 	virtual void f() { std::cout << "Base::f" << std::endl; }
 	virtual void g() { std::cout << "Base::g" << std::endl; }
	
        int i;

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

	int j;
};
这是最复杂、最正常的基类,图如下:

为了清晰起见,图中给出某次运行时的实际地址,这里相邻地址总是正好4字节,因为我的系统int与指针类型是4字节,

如果是其他类型,未必是4,还可能要考虑内存对齐的问题,不属于这篇文章的范畴……

正常基类的虚表总是以虚析构代理函数开头、中间接虚函数、以结束标记结尾,就算f比~Base()先声明,结果也是一样。

1.5 含纯虚函数的抽象基类

class Base
{
public:	
	Base():i(3),j(4){}
	virtual ~Base(){}

 	virtual void f()=0;
 	virtual void g(){ std::cout << "Base::g" << std::endl; }

	int i;

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

	int j;
};

由于抽象基类的对象没有办法构造,其指针类型也只能指向派生类实例,所以似乎没有办法测试基类的内存结构,但可以从派生类对象上大体推断:

其内存结构与1.4的图并无区别

二、派生类内存结构

2.1 继承自抽象类的(1.5)、重写纯虚函数的派生类

class Derived: public Base
{
public:
	virtual void f() { std::cout << "Derived::f" << std::endl; }
};
这里是为了承接1.5的"推断",给个最为简单的测试代码:

	Derived dObj;
	Base* pb = &dObj;
调试,展开pb之+,可以看到:

这里虽然是dObj的虚表, 但基本可以证明Base的虚表内存结构(将图中Derived换成Base即可)

2.2 继承自1.4,重写f、添加自己的虚函数和数据成员的派生类

class Derived: public Base
{
public:
	Derived():k(5){}
	virtual void f() { std::cout << "Derived::f" << std::endl; }
	virtual void f1() { std::cout << "Derived::f1" << std::endl; }

private:
	virtual void g1() { std::cout << "Derived::g1" << std::endl; }
	int k;
};
这个是最正常用法,先上图

是时候给出测试代码了:

v
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值