内存神奇又神秘,让我们一探究竟吧!
VS打开工程,右键工程名称—>属性—>c/c++—>命令行—> /d1 reportAllClassLayout
空类
class emptyClass{};
编译该类发现空类大小为1字节,如下图所示:
为什么空类大小为1字节:因为c++有规定,任何不同的对象都应该有不同的地址。空类虽然没有内容,但是仍旧需要分配地址。
基础类
class commonClass
{
int i;
char c;
};
该类占用8个字节(四字节对齐,所以需要八个字节),如下图所示:
带虚函数的类
class commonClass
{
int i;
char c;
virtual void virt_fun1(){}
virtual void virt_fun2(){}
void comm_fun1(){}
void comm_fun2(){}
};
该类占用12个字节,如下图所示:
四字节对其2个变量共占用8个字节,由于存在虚函数,所以在类开始位置插入了一个虚函数指针,该指针占用4个字节,共计12字节。类的非虚函数其实不占用类对象的内存(函数编译后形成二进制文件放在内存中的代码段区)
简单继承
class baseClass
{
int i;
char c;
};
class inheritClass : public baseClass
{
char c;
};
inheritClass占用12个字节,并且内存中基类在派生类的前面。从内存角度来看,这也是类向上转换的基础。
基类带有虚函数
派生类和基类存在同名的虚函数。由于派生类实现了基类的虚函数,所以即使是向上转换,也会调用派生类的虚函数
#include "iostream"
using namespace std;
class baseClass
{
private:
int i;
char c;
public:
virtual void virt_fun1(){ cout << "baseClass virt_fun1" << endl; }
virtual void virt_fun2(){ cout << "baseClass virt_fun2" << endl; }
void comm_fun1(){ cout << "baseClass comm_fun1" << endl; }
void comm_fun2(){ cout << "baseClass comm_fun2" << endl; }
};
class inheritClass : public baseClass
{
private:
char c;
public:
virtual void virt_fun1(){ cout << "inheritClass virt_fun1" << endl; }
virtual void virt_fun3(){ cout << "inheritClass virt_fun2" << endl; }
void comm_fun1(){ cout << "inheritClass comm_fun1" << endl; }
void comm_fun3(){ cout << "inheritClass comm_fun3" << endl; }
};
int _tmain(int argc, _TCHAR* argv[])
{
inheritClass a;
a.virt_fun1();
baseClass *b = new inheritClass();
b->virt_fun1();
b->comm_fun1();
inheritClass *c = (inheritClass*)b;
c->virt_fun1();
c->comm_fun1();
c->comm_fun2();
c->comm_fun3();
return 0;
}
b->virt_fun1()调用派生类的虚函数,程序输出如下:
派生类在内存中占用16字节,内存分布如下:
从内存分布中可以看到:
1:非虚继承时基类在派生类的前面,故可以向上转换,同时也可将向上转换后的指针强制转换为派生类指针。
2:基类和派生类使用同一个虚函数表。虚函数表的排列顺序遵循先基类后派生类,由于基类和派生类存在同名的虚函数(virt_fun1),所以派生类的虚函数覆盖了基类的同名虚函数从而排到了最前面。
3:
虚继承
#include "iostream"
using namespace std;
class baseClass
{
private:
int i;
char c;
public:
void virt_fun1(){ cout << "baseClass virt_fun1" << endl; }
void virt_fun2(){ cout << "baseClass virt_fun2" << endl; }
void comm_fun1(){ cout << "baseClass comm_fun1" << endl; }
void comm_fun2(){ cout << "baseClass comm_fun2" << endl; }
};
class inheritClass : public virtual baseClass
{
private:
char c;
public:
void virt_fun1(){ cout << "inheritClass virt_fun1" << endl; }
void virt_fun3(){ cout << "inheritClass virt_fun2" << endl; }
void comm_fun1(){ cout << "inheritClass comm_fun1" << endl; }
void comm_fun3(){ cout << "inheritClass comm_fun3" << endl; }
};
int _tmain(int argc, _TCHAR* argv[])
{
inheritClass a;
a.virt_fun1();
baseClass *b = new inheritClass();
b->virt_fun1();
b->comm_fun1();
// inheritClass *c = (inheritClass*)b; 无法直接向上转换
inheritClass *c = (inheritClass*)((char*)b - 8);
c->virt_fun1();
c->comm_fun1();
c->comm_fun2();
c->comm_fun3();
return 0;
}
程序输出:
派生类在内存中占用16字节,内存分布如下:
从内存分布可以看出:
1:由于基类和派生类都没有虚函数,当派生类虚继承基类时,派生类的内存中增加了一个虚函数表
2:派生类在内存中的位置排在了基类的前面,所以可以向上转换,但是向上转换后的指针无法再强制类型转换为派生类指针。(由于基类在内存中的地址和派生类的地址不同,所以无法直接强制类型转换)
3:由于向上转换后,基类地址比派生类地址多了8个字节,所以可以通过将基类地址减去8的方法找到派生类地址
带虚函数的虚继承
#include "iostream"
using namespace std;
class baseClass
{
private:
int i;
char c;
public:
virtual void virt_fun1(){ cout << "baseClass virt_fun1" << endl; }
virtual void virt_fun2(){ cout << "baseClass virt_fun2" << endl; }
void comm_fun1(){ cout << "baseClass comm_fun1" << endl; }
void comm_fun2(){ cout << "baseClass comm_fun2" << endl; }
};
class inheritClass : public virtual baseClass
{
private:
char c;
public:
virtual void virt_fun1(){ cout << "inheritClass virt_fun1" << endl; }
virtual void virt_fun3(){ cout << "inheritClass virt_fun2" << endl; }
void comm_fun1(){ cout << "inheritClass comm_fun1" << endl; }
void comm_fun3(){ cout << "inheritClass comm_fun3" << endl; }
};
int _tmain(int argc, _TCHAR* argv[])
{
inheritClass a;
a.virt_fun1();
baseClass *b = new inheritClass();
b->virt_fun1();
b->comm_fun1();
// inheritClass *c = (inheritClass*)b;
inheritClass *c = (inheritClass*)((char*)b - 12);
c->virt_fun1();
c->comm_fun1();
c->comm_fun2();
c->comm_fun3();
return 0;
}
程序输出:
派生类在内存中占用24字节,内存分布如下:
从内存分布中可以看出:
1:基类和派生类都有独立虚函数表vbptr,由于是虚继承,所以此时派生类增加一个vfptr的指针
2:虚继承时派生类的地址和基类地址不同,基类地址高于派生类地址
3:虚继承依旧支持向上转换。但是向上转换后的指针不能直接强制类型转换为派生类的指针
4:可以通过地址偏移将向上转换后的基类指针强制类型转换为派生类指针