测试情形描述:
A类有public成员变量、private成员变量、2个虚函数show()、show_a()
B类有public成员变量、private成员变量、2个虚函数show()、show_b()
C类继承A和B,C有自己的public成员变量、private成员变量、2个虚函数show()、show_c(),显然A和B的虚函数show都被C的show覆盖了,show_a()和show_b()没有被覆盖
下面,我们来看一下C类的对象的各个成员(包括继承来的成员)在内存中的布局
源码如下:QT5,6.1编译通过
#include <QCoreApplication>
#include "qglobal.h"
#include <iostream>
//多继承的类的对象的成员在RAM中的分布
class A
{
public:
A(int pub_tmp=0):
m_a(pub_tmp){ m_pri_a = 100; }
int m_a;
void add()
{
m_a++;
}
virtual void show(void)
{
printf("call A::show() %d, %d\n", m_a, m_pri_a);
}
virtual void show_a(void)
{
printf("call A::show_a()\n");
}
private:
int m_pri_a;
};
class B
{
public:
B(int pub_tmp=0):
m_b(pub_tmp){m_pri_b = 200; }
int m_b;
void add()
{
m_b++;
}
virtual void show(void)
{
printf("call B::show() %d, %d\n",m_b, m_pri_b);
}
virtual void show_b(void)
{
printf("call B::show_b()\n");
}
private:
int m_pri_b;
};
class C:public A,public B
{
public:
C(int pub_tmp=0):
A(pub_tmp),
B(pub_tmp),
m_c(pub_tmp){ m_pri_c = 300; }
int m_c;
void add()
{
m_c++;
}
virtual void show(void)
{
printf("call C::show() %d, %d\n",m_c, m_pri_c);
}
virtual void show_c(void)
{
printf("call C::show_c()\n");
}
private:
int m_pri_c;
};
typedef void (*pFunc)(void);
int main()
{
printf("start\n");
printf("sizeof(int) = %d\n", sizeof(int));
C obj_c(3);
A *pA_c = (A*)&obj_c;
B *pB_c = (B*)&obj_c;
C *pC_c = &obj_c;
printf("pA_c = %d\n", (int)pA_c);//A、C虚表的地址的地址
printf("pB_c = %d\n", (int)pB_c);//B虚表的地址的地址
printf("pC_c = %d\n", (int)pC_c);//A、C虚表的地址的地址
printf("&obj_c.m_a = %d\n", (int)&obj_c.m_a);
printf("&obj_c.m_b = %d\n", (int)&obj_c.m_b);
printf("&obj_c.m_C = %d\n", (int)&obj_c.m_c);
printf("obj_c.m_pri_a = %d\n", *((&obj_c.m_a)+1));//可以读到A的私有成员m_pri_a
printf("obj_c.m_pri_b = %d\n", *((&obj_c.m_b)+1));//可以读到B的私有成员m_pri_b
printf("obj_c.m_pri_b = %d\n", *(int *)((char*)(pC_c)+sizeof(A)+4+4));//可以读到B的私有成员m_pri_b
pFunc pShow;
int *addr_v_table_AC = (int*)*(int*)pA_c;//获取对象c的A虚表和C虚表的地址(见下文注释)
pShow = (pFunc)*addr_v_table_AC;//从虚表中取出第1个虚函数的地址
pShow();
pShow = (pFunc)*(addr_v_table_AC+1);//从虚表中取出第2个虚函数的地址
pShow();
pShow = (pFunc)*(addr_v_table_AC+2);//从虚表中取出第3个虚函数的地址
pShow();
int *addr_v_table_B = (int*)*(int*)pB_c;//获取对象c的B虚表的地址
pShow = (pFunc)*addr_v_table_B;//从虚表中取出第1个虚函数的地址
pShow();
pShow = (pFunc)*(addr_v_table_B+1);//从虚表中取出第2个虚函数的地址
pShow();
return 0;
}
运行结果为:
根据运行结果,很容易得到以下3张表格,本例中,C的对象obj_c的成员的布局如下:
起始地址 | 字节数 | 内容 | 对象c是否可以直接访问 |
pC_c=pA_c=2686584 | 4 | A、C虚表的地址 | X (隐藏的成员) |
2686588 | 4 | 从A继承来的public变量m_a | 是 |
2686592 | 4 | 从A继承来的private变量m_pri_a | X |
pC_b=2686596 | 4 | B虚表的地址 | X(隐藏的成员) |
2686600 | 4 | 从B继承来的public变量m_b | 是 |
2686604 | 4 | 从B继承来的private变量m_pri_b | X |
2686608 | 4 | C自己的public变量m_c | 是 |
2686612 | 4 | C自己的private变量m_pri_c | 是 |
C的对象obj_c内存中AC虚表和B虚表中的内容如下:
AC虚表的内容 | 备注 |
C::show的地址 | A::show被C::show覆盖了 |
A::show_a的地址 | A::show_a没有被覆盖 |
C::show_c的地址 | C中没有覆盖父类虚函数的虚函数(C独有的虚函数)地址表从这里开始向下排列 |
B虚表的内容 | 备注 |
C::show的地址 | B::show被C::show覆盖了 |
B::show_b的地址 | B::show_b没有被覆盖 |
对上述结果作简要总结:
- 如果某个类D带有虚函数,且它不继承别的类,或者其继承的层层父类都没有虚函数,那么其这个类D的对象内存空间的首地址处就会被编译器自动插入一个隐藏的成员变量:虚表地址。如果该类D被别的类继承了,虚表地址作为一个成员变量,也会被继承过去,只是虚表地址指向的内容可能会因覆盖现象而有所变化。
- 如果某个类D带有虚函数,且它继承的层层父类中至少有一个带虚函数,那么编译器就不用给这个类D插入虚表地址了,因为它已经继承了某个祖宗类的虚表地址,类D的非覆盖虚表会被放在继承来的第一张虚表的后面。
- 如果某个类D不带虚函数,但是其父类有虚表地址,那么其对象内存空间的首地址处也是个虚表地址,这个虚表地址是作为一个特殊的成员变量从父类继承过来的。
- C类继承了带虚函数的A和带虚函数的B,那么C的对象中就会有2个虚表地址,指向两张虚表,我把它们称为A虚表和B虚表,而C本身也有自己的虚函数,C的非覆盖虚表(为何叫非覆盖虚表,因为这张表不含覆盖过父类虚函数的虚函数地址,而覆盖过父类虚函数的虚函数地址已经被替换进A、B的虚表中了)的内容会被放到第一张虚表的后面,也即A虚表的后面,这样第一张虚表我称它为AC虚表。
- C的虚函数有些(如C::show)覆盖了父类的虚函数,有些(如C::show_c())没有覆盖父类的虚函数;父类A的虚函数有些(如A::show)被子类覆盖了,有些(如A::show_a)没有被子类覆盖。
- 任何类的成员函数、静态成员变量都不占用对象的内存空间。占用空间的只有非静态成员变量和虚表地址(虚表地址,本质上也相当于一个非静态成员变量),虚表地址会占用N*Size字节的空间,N为虚表的个数,Size为虚表地址所占字节数,本例中虚表地址为4字节。
更进一步,如果再有:class D:public C,D也有两个虚函数show()和show_d(),那么D的对象的布局会是什么样?测试代码就不放了,要不然这篇文章就太长了,直接给出结果:
首先,C的布局(也即上面的第一张表格的样子)会被完全复制下来,然后C的布局后面紧接着是D的成员变量。
需要注意的是:copy过来的C布局中包含了两个虚表地址,一个指向AC虚表,另一个指向B虚表,AC虚表的内容后面又会添加D的非覆盖虚表,当然增加的这部分内容并不在D的成员布局里,而是增加在了AC虚表地址指向的内存中,
这样D的布局里就有两个虚表地址,一个指向ACD虚表,另一个指向B虚表。这种情形下,如果我们使用A、C、D的指针指向D的对象,这3个指针的值是一模一样的。