一 普通类的内存模型:
从一段简单的代码开始
[C++] 纯文本查看 复制代码
class Test {
public:
char a;
double b;
char c;
static int d;
static int e;
void fun()
{
printf("Hello world");
}
};
int _tmain(int argc, _TCHAR* argv[])
{
Test Teobj;
Teobj.a = 'a';
Teobj.b = 1564654.325;
Teobj.c = 'b';
cout << sizeof(Test);
}
输出结果为24;
我们把上一篇的例子中struct改成class关键字,再在class中添加pubilc属性,保证在外部能访问数据,又添加了两个静态成员和一个成员函数。显示结果与结构体是一模一样的。
由此可以验证:
1 vs2012c++中结构体与类的区别只是内部默认访问属性的不同。
2 对于一个没有继承别的类的类,他的内存模型与结构体一模一样,也就是说内存对齐也是一样的。
3 静态成员与成员函数不会对内存模型造成任何影响。
二 有继承关系时对象的内存模型:
这一部分才是我们的正题,在开始之前先思考这样一个问题,请看图
类B派生自类A,类C除了没有继承自类A外,与类B一模一样。所有的类没有虚继承与虚函数,此时:
sizeof(类A)+sizeof(类C)与sizeof(类B)之间的大小关系如何呢?
OK! Let`s the killing begin。
我们此时讨论的是没有虚函数,也不虚继承的派生类
using namespace std;
class Base
{
public:
Base() :a(0x1), b(0x2), d('a'), e('a') {}
int a;
int b;
int c;
double d;
char e;
};
class Inherit1 :public Base
{
public:
Inherit1() :m_Inherit1a(0xF) {}
int m_Inherit1a;
};
class CTest
{
public:
CTest() :m_Inherit1a(0xF) {}
int m_Inherit1a;
};
int main() {
Base obj1;
CTest obj2;
Inherit1 obj3;
cout << "基类大小 :" << sizeof(obj1) << endl;
cout << "测试类大小:" << sizeof(obj2) << endl;
cout << "子类大小 :" << sizeof(obj3) << endl;
return 0;
}
输出的结果为:
我们慢慢分析这个结果:
首先来看一看子类对象,他的大小为40,内存模型如下图:
可以看出前三个值是1,2,3,也就是在基类构造函数中初始化的数据成员,可以验证,在派生类对象中,开始存放的是基类的数据成员。前3个int型成员占据12个字节后,之后是double型,那么所在位置偏移需要为8的整数倍故而隔了4个字节开始存放成员d,随后存放成员e,此时基类大小为25,但是基类总大小应该为8的整数倍,所以后面又空置了7个字节。
随后存储的是派生类中的数据成员,仅有一个int型,总大小是36,本以为到这里应该结束,然而事实并非如此,整个派生类对象的对齐同时考虑基类和子类,故而大小为8的整数倍,后面又补了4个字节,共40字节。
我们让这两个类没有继承关系,能够看到一个是32字节,一个是4字节,有了继承关系组合到一起后竟然超过没有继承关系时两个类大小的总和,这个现象也是挺有意思的。
基类与派生类会融合吗?
#include <iostream>
using namespace std;
class Base
{
public:
Base() :a(0x1), b(0x2) {}
int a;
char b;
};
class Inherit1 :public Base
{
public:
Inherit1() :m_Inherit1a(0xF) {}
char m_Inherit1a;
};
class CTest
{
public:
CTest() :m_Inherit1a(0xF) {}
char m_Inherit1a;
};
int _tmain(int argc, _TCHAR* argv[])
{
Base obj1;
CTest obj2;
Inherit1 obj3;
cout << "基类大小 :" << sizeof(obj1) << endl;
cout << "测试类大小:" << sizeof(obj2) << endl;
cout << "子类大小 :" << sizeof(obj3) << endl;
return 0;
}
子类内存模型如下:
可以看出,临近的两个char类型数据并没有融合到一起,基类与子类泾渭分明。并且又由于整体对齐的原因,又是继承关系的子类大小大于基类与测试类大小的总和。
接下来,我们再看一段代码:
#include <iostream>
using namespace std;
class Base
{
public:
Base() :a(0x1), b(0x2) {}
int a;
char b;
};
class Inherit1 :public Base
{
public:
Inherit1() :m_Inherit1a(0xF) {}
char m_Inherit1a;
};
class CTest
{
public:
CTest() :m_Inherit1a(0xF) {}
char m_Inherit1a;
};
int _tmain(int argc, _TCHAR* argv[])
{
Base obj1;
CTest obj2;
Inherit1 obj3;
cout << "基类大小 :" << sizeof(obj1) << endl;
cout << "测试类大小:" << sizeof(obj2) << endl;
cout << "子类大小 :" << sizeof(obj3) << endl;
return 0;
}
#include <iostream>
using namespace std;
class Base
{
public:
Base() :a(0x1), b(0x2), c(0x3), d('a'), e('a') {}
int a;
int b;
int c;
int d;
char e;
};
class Inherit1 :public Base
{
public:
Inherit1() :c(0x1), m_Inherit1a(0xF) {}
char c;
double m_Inherit1a;
};
class CTest
{
public:
CTest() :c(0x1), m_Inherit1a(0xF) {}
char c;
double m_Inherit1a;
};
int main() {
Base obj1;
CTest obj2;
Inherit1 obj3;
cout << "基类大小 :" << sizeof(obj1) << endl;
cout << "测试类大小:" << sizeof(obj2) << endl;
cout << "子类大小 :" << sizeof(obj3) << endl;
return 0;
}
子类内存模型如下:
可以看出,父类与子类,依然泾渭分明,子类部分被挤榨的仅为12个字节,整体上来说32字节还是保证了为8的倍数。此时就出现了鬼神难料的一步,具有继承关系的子类的大小竟然小于了父类与测试类大小的和。
总结一下:
1 当为普通继承关系时,基类成员在派生类成员的上面
2 基类与子类的成员不会融合,也就是说,会保证基类大小是对齐过的。
3 无论是基类成员还是子类成员,排布其所在位置的偏移都是相对于整个类对象的起始位置。
4 静态成员与成员函数对于类对象的内存排布没有任何影响。
我们现在可以回答一开始提出的问题了
有可能sizeof(类A)+sizeof(类C)>sizeof(类B),也可能sizeof(类A)+sizeof(类C)<sizeof(类B),也可能sizeof(类A)+sizeof(类C)==sizeof(类B).
事情到这里本就应该结束了,不过都看到这了,就再探讨一下子类与父类之间的关系。
(本文中某些问题还可以再做进一步探讨,这是原作者所没有探讨的,如本文中前半部分子类都是以公共部分继承的,如果以非公共部分继承时将会发生怎样的变化,这可以继续探讨)