因为挺多内容的来自别人博客的学习,所以把相关博客的链接拉上:
虚函数解析:http://blog.csdn.net/haoel/article/details/1948051
C++ 对象的内存布局(上):http://blog.csdn.net/haoel/article/details/3081328
C++对象的内存布局(下):http://blog.csdn.net/haoel/article/details/3081385
在这篇博客里面,我们将致力于利用GDB工具来分析以下几种情况的对象内存模型:
1.对象的虚函数表是怎么回事?
2.对象的虚函数是怎么分布的?
3.函数继承的虚函数表是怎么样的?
4.子类复写了父类的函数的结果虚函数表会怎样构成?
5.有成员变量的情况应该怎么处理?
在开始之前,我想先说几个基本概念,这几个基本概念可以帮助我们快速理解对象的内存是如何分配的?
1.C++的函数对象构造过程是从基类开始构造,然后在构造子类。就像搭房子一样,先盖基底然后再盖上层建筑。那么,析构的顺序也是相同的,先调用子类的析构函数,然后再调用基类的析构函数,就跟拆房子先从上层开始拆。
2.我们为何要使用虚函数、虚类,虚继承的目的。从目的出发,我们使用虚函数的最大目的是为了多态,多态的目的就是基类给你指定函数的接口形式,然后子类再去overwrite,这就必然会涉及到函数的覆盖。覆盖的目的就是换一个新的函数地址,这就需要一个虚函数表的数据结构。那么,反过来说。如果不是虚函数,那么我们子类的目的就是可以使用基类的函数,这就不需要虚函数表这个数据结构,而是正常的在this指针里面指向的空间并且调用。
3.顺序问题:虚基类(虚继承)放在this指针指向的空间的最后部分。所以,我们想要访问共有的基类元素就可以经过访问虚基类指针访问虚基类表中的偏移量,通过偏移量来获得我们的基类共有元素。
4.本类的虚函数存储在第一个虚函数表的最后一个。
5.虚函数表的目的是为了重载,之前一直犯了个错误。认为对象的函数都需要一个函数指针来指向。但是实际上,类似C语言的函数声明一样,非虚函数都已经通过函数声明声明好了。
好了,有了这些元素,我们就可以进行分析了:
首先,我们先来分析最简单的例子,不考虑虚继承,但是有非虚函数:
class Vertex3d
{
public:
void Foo(){printf("Vertex3d::Foo() called !\n");}
};
class MY:public Vertex3d
{
public:
void print(){printf("Hello,world!!");}
};
int main()
{
printf("%lu\n",sizeof(MY));
return 0;
}
结果是1。
只有一个字节,可见对象里面的函数并不会占据空间。
其次,我们想再难一点,这个时候,我们讨论一下当虚函数加入其中的情况:
#include<iostream>
using namespace std;
class Base {
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
};
class MY:public Base
{
public:
void test(){}
};
int main()
{
printf(