测试环境:MSVC2013 32位
1. 无继承并且不含虚函数
#include <iostream>
using namespace std;
class A
{
private:
int m_a;
int m_i;
};
int main()
{
A a;
return 0;
}
对象及其成员内存地址,这里成员在对象中排列顺序和成员定义顺序有关,由于是int类型所以相差4
另外还可以用VS命令行下查看虚函数表和类内存布局命令如下:
参考:https://blog.csdn.net/daydreamingboy/article/details/8982563
// 这里的A是我要查看内存结构的类的名称,Test.cpp是我的源文件,可以根据情况自己更换
cl /d1 reportSingleClassLayoutA Test.cpp
如图:
2. 无继承含有虚函数
#include <iostream>
using namespace std;
class A
{
private:
int m_a;
int m_i;
public:
virtual void Coding() {
cout << "A::Coding()" << endl;
}
};
int main()
{
A a;
return 0;
}
注意这里的&a != &a.m_a相差4而上面那个不带虚函数的例子它们俩是相同的是什么占用了4字节的内存呢?
通过命令提示符查看类内存布局则显而易见,vfptr是一个虚函数表指针,在这里占用四个字节并且放在最前面偏移地址位为0
3. 单继承并且虚函数无覆盖
#include <iostream>
using namespace std;
class A
{
private:
int m_a;
int m_i;
public:
virtual void Coding() {
cout << "A::Coding()" << endl;
}
};
class B : public A
{
private:
int m_b;
public:
void Drinking() {
cout << "B::Drinking()" << endl;
}
};
int main()
{
B b;
return 0;
}
通过成员内存地址发现这里子类B的新添成员直接放在了父类成员的后面,注意这里高亮的一行次序函数表中存放的是父类A的Coding函数,因为无覆盖,下面将探讨有覆盖的情况
类内存布局如下:
4. 单继承并且虚函数有覆盖
#include <iostream>
using namespace std;
class A
{
private:
int m_a;
int m_i;
public:
virtual void Drinking() {
cout << "A::Drinking()" << endl;
}
virtual void Coding() {
cout << "A::Coding()" << endl;
}
};
class B : public A
{
private:
int m_b;
public:
virtual void Drinking() {
cout << "B::Drinking()" << endl;
}
virtual void Wc() {
cout << "B::Wc()" << endl;
}
};
int main()
{
B b;
return 0;
}
这里我为了突出区别不但定义有覆盖的虚函数,而且还定义了各自私有的无覆盖的虚函数,注意看这里高亮的Coding函数它是子类B的Coding函数,和上面那个例子不一样,因为它是有覆盖的,这就是多态的实现,细心的读者可能会发现,为什么虚函数表中只有两个虚函数?子类私有的那个虚函数Wc哪里去了?这里我也不太清楚为什么不显示,我觉得肯定是有的,那么我们就直接看类的内存布局找一找。
内存布局:通过内存查看显而易见,在虚函数表2号槽位置出现了子类私有的虚函数Wc,解决了上面的疑问,只是不清楚上面的监视面板为啥不显示,忘各位大神指点!!!
5. 虚继承并且子类父类均无虚函数
#include <iostream>
using namespace std;
class A
{
private:
int m_a;
int m_i;
};
class B : public virtual A
{
private:
int m_b;
};
int main()
{
B b;
cout << sizeof(b) << endl;
return 0;
}
这是虚继承的各成员的内存分布,有没有发现什么不同?这里子类成员m_b跑到前面去了,而不是如前面探讨的接在父类成员m_a,m_i的后面?还有一个特别之处就是为什么对象b和成员m_b的相差4字节而从监视面板中并没有看见有其他的成员,这4个字节被谁占去了????(只有通过类内存布局看究竟了~~~)
从类内存布局发现,m_b确实跑到了前面,而且在它前面出现了一个形如vfptr的vbptr,这下我们知道了就是这个vbptr占去了四字节的内存,它就是虚基类表指针!!!
6. 虚继承并且父类有虚函数
#include <iostream>
using namespace std;
class A
{
private:
int m_a;
int m_i;
public:
virtual void Coding() {
cout << "A::Coding()" << endl;
}
};
class B : public virtual A
{
private:
int m_b;
};
int main()
{
B b;
cout << sizeof(b) << endl;
return 0;
}
从各成员的内存地址可以看出,当父类有虚函数并且是虚继承的时候,虚函数表指针vfptr是放在子类最后一个成员下面的,这与前面无虚继承的时候是不同的!!!
类内存布局:从这可以看出vbptr是在最上面的
7. 虚继承并且子类有虚函数
#include <iostream>
using namespace std;
class A
{
private:
int m_a;
int m_i;
};
class B : public virtual A
{
private:
int m_b;
public:
virtual void Coding() {
cout << "A::Coding()" << endl;
}
};
int main()
{
B b;
cout << sizeof(b) << endl;
return 0;
}
从下面可以看出,当只有子类有虚函数的虚继承时,和第6种情况有不同了!!!这里的子类成员m_b又跑到了vfptr的下面了!!!但子类成员还是在父类其它成员的上面的,因为vbptr在它们的上面。
类内存布局:
8. 虚继承并且父类子类都有虚函数而且有覆盖
#include <iostream>
using namespace std;
class A
{
private:
int m_a;
int m_i;
public:
virtual void Drinking() {
cout << "A::Drinking()" << endl;
}
virtual void Coding() {
cout << "A::Coding()" << endl;
}
};
class B : public virtual A
{
private:
int m_b;
public:
virtual void Coding() {
cout << "B::Coding()" << endl;
}
virtual void Wc() {
cout << "B::Wc()" << endl;
}
};
int main()
{
B b;
cout << sizeof(b) << endl;
return 0;
}
看图?为什么这种情况下子类特有的虚函数就乖乖地出现在监视面板了呢?第4种情况下死活不出现。我也不知道为什么vs会这样。。。。
下面分析类内存布局:从上面这几种情况是不是觉得虚继承好可怕啊!!!带来的不同太多了!!!,我们是不是可以先小总结一下,当虚继承的时候子类的成员总是放在虚基类指针的下面不分离,而虚函数表指针和其它成员函数就不一定喽~~~,这种情况下和第4种情况也有很大不同(多了个vfptr而且成员位置也大有不同),尽管只是比第4种情况多了个虚继承!!!
下面进入多继承环节吧。。。。
9. 普通多继承
#include <iostream>
using namespace std;
class A
{
private:
int m_a;
int m_i;
};
class B
{
private:
int m_b;
};
class C : public A, public B
{
private:
int m_c;
};
int main()
{
C c;
cout << sizeof(c) << endl;
return 0;
}
观看成员地址,没有什么特别的,一个一个接着排列,子类成员在最下面,不过父类A和父类B的成员分布就有待考虑了,和继承顺序有关,我这里是先继承的A后继承的B,如果A,B互换的话就不同了,下面我只把类内存布局图贴出来,一看便知~~~
类内存布局图:
10. 多继承父类有两个有虚函数一个没有虚函数,子类没有虚函数
#include <iostream>
using namespace std;
class A
{
private:
int m_a;
int m_i;
public:
virtual void Coding() {
cout << "A::Coding()" << endl;
}
};
class B1
{
private:
int m_b1;
};
class B2
{
private:
int m_b2;
public:
virtual void Coding() {
cout << "B2::Coding()" << endl;
}
};
class C : public B1, public A, public B2
{
private:
int m_c;
};
int main()
{
C c;
cout << sizeof(c) << endl;
return 0;
}
这里举了一个综合的例子,其中父类A中有虚函数,父类B1中没有,父类B2中也有虚函数,猜一猜对象内存布局如何呢???
总结:此时哪个父类含有虚函数就是谁的成员变量放在前面的先决条件了,因为这次我把没有虚函数的父类B1最先继承,然而并没什么用,它的成员还是被乖乖放在了其它两个基类成员的后面了(因为B1没虚函数!!!)其次才是继承顺序(如A和B2成员分布对比先继承A它的成员就放在了最上面)
如图:
10. 菱形继承非虚继承并且都不带虚函数
#include <iostream>
using namespace std;
class A
{
private:
int m_a;
int m_i;
};
class B1 : public A
{
private:
int m_b1;
};
class B2 : public A
{
private:
int m_b2;
};
class C : public B1, public B2
{
private:
int m_c;
};
int main()
{
C c;
cout << sizeof(c) << endl;
return 0;
}
类内存布局图:如图对象C内出现了两份类A的成员,这个时候如果直接c.m_a直接访问的话会出现不明确的错误,我们需要指令类作用域例如:c.B1::m_a这样就可以了,还有一种方法就是虚继承接下来再讨论。这里的基类B1,B2的排布顺序和继承顺序有关,(我这里先继承的B1后继承的B2)
注意这里c的成员m_c是放在最后面的,即基类B2成员的后面!!!
11. 菱形继承虚继承
#include <iostream>
using namespace std;
class A
{
private:
int m_a;
int m_i;
};
class B1 : public virtual A
{
private:
int m_b1;
};
class B2 : public virtual A
{
private:
int m_b2;
};
class C : public B1, public B2
{
private:
int m_c;
};
int main()
{
C c;
cout << sizeof(c) << endl;
return 0;
}
可以发现此时,对象c中就只含一份基类A的成员了,并且多了两个vbptr,这个时候再使用c.m_a访问的时候就不会出现成员不明确的错误了。
还有多种情况就不在一一分析下去了~~~~
注意:MSVC编译器与G++的下生成的C++类实例内存布局是不同的!!!!
g++编译器生成的C++类实例,虚函数与虚基类地址偏移值共享一个虚表(vtable)。类实例的开始处即为指向所属类的虚指针(vptr)。实际上,一个类与它的若干祖先类(父类、祖父类、…)组成部分共享一个虚表,但各自使用的虚表部分依次相接、不相重叠。
g++编译下,一个类实例的虚指针指向该类虚表中的第一个虚函数的地址。如果该类没有虚函数(或者虚函数都写入了祖先类的虚表,覆盖了祖先类的对应虚函数),因而该类自身虚表中没有虚函数需要填入,但该类有虚继承的祖先类,则仍然必须要访问虚表中的虚基类地址偏移值。这种情况下,该类仍然需要有虚表,该类实例的虚指针指向类虚表中一个值为0的条目。
Microsoft Visual C++与g++不同,把类的虚函数与虚基类地址偏移值分别放入了两个虚表中,前者称为虚函数表vftbl,后者称虚基类表vbtbl。因此一个类实例可能有两个虚指针分别指向类的虚函数表与虚基类表,这两个虚指针分别称为虚函数表指针vftbl与虚基类表指针vbtbl。当然,类实例也可以只有一个虚指针,或者没有虚指针。虚指针总是放在类实例的数据成员之前,且虚函数表指针总是在虚基类表指针之前。因而,对于某个类实例来说,如果它有虚基类指针,那么虚基类指针可能在类实例的0字节偏移处,也可能在类实例的4字节偏移处(对于32位程序来说),这给类成员函数指针的实现带来了很大麻烦。***
参考链接:
https://zh.wikipedia.org/wiki/%E8%99%9A%E7%BB%A7%E6%89%BF
***在这里我把我用搜索引擎搜到的一个在G++下查看类内存布局的命令放在这里:
参考链接:
https://www.cnblogs.com/malecrab/p/5573368.html
命令作用后会在工作目录下产生一个.class文件然后直接用vim打开.class文件就可以看见了如MSVC命令提示符下产生的类内存布局图了!!!(温馨提示:源文件最好不要包含#include 头文件不然会产生很多奇怪的东西!!!以至于内存布局图不好查找!!!)***
// 这里的test.cpp是我源文件的名字
g++ -fdump-class-hierarchy test.cpp
例图:
感兴趣的读者可以自己测试一下其它情况~~~~