关于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