一、C++ 对象模型
1、对象内存布局
在C++中,有两种数据成员:
static
和nonstatic
,以及三种成员方法static
、nonstatic
、virtual
,下面从虚函数、非虚函数、静态成员变量、非静态成员变量等维度来分析,类对象的内存布局。例如:下面定义一个Point
类,包含前面四种类型的成员
#include <iostream>
using namespace std;
class Point
{
public:
Point(double val);
virtual ~Point(); // virtual function numbers
double GetPoint(); // notstatic function numbers
static int PointCount(); // static function numbers
private:
double m_x; // notstatic data numbers
static int m_count; // static data numbers
};
int Point::m_count = 0;
Point::Point(double val):m_x(val) {}
Point::~Point() {}
double Point::GetPoint(){ return m_x; }
int Point::PointCount(){ return m_count; }
int main()
{
Point pt(10.0);
cout << sizeof(pt) << endl; // 输出结果:16
return 0;
}
一个实例对象中包含
非静态数据成员
、虚表指针
以及为对齐而必需的填充
、静态成员变量
、函数
独立于单个实例化对象。
总结:影响C++对象大小的三个因素:
非静态数据成员
、虚函数
和字节对齐
2、空对象
C++规定空类对象大小至少为1字节,只是为了
区分
实例化对象。如果创建了多个空类的对象,可以通过对象的内存地址区分。例如:下面创建一个空类Empty
#include <iostream>
using namespace std;
class Empty
{
};
int main()
{
Empty e;
cout << sizeof(e) << endl; // 输出结果:1
return 0;
}
3、数据成员的声明顺序与内存布局
3.1、声明顺序与内存布局
同一访问级别
的非静态数据成员
声明顺序与内存中的布局是一致的(即:先声明的非静态数据成员,先分配内存地址)。不同访问控制级别的非静态数据成员,未规定内存分配顺序,但是实际上,编译是按照声明顺序来安排内存,例如:定义一个Point3d
类,并打印出成员地址
#include <iostream>
using namespace std;
class Point3D
{
public:
void Print() const {
cout << "this addr " << this << endl;
cout << "m_x addr " << &m_x << endl;
cout << "m_y addr " << &m_y << endl;
cout << "m_z addr " << &m_z << endl;
}
private:
int m_x;
int m_y;
int m_z;
};
int main()
{
Point3D obj;
obj.Print();
return 0;
}
输出结果
this addr 0x61fe14
m_x addr 0x61fe14
m_y addr 0x61fe18
m_z addr 0x61fe1c
Process returned 0 (0x0) execution time : 0.241 s
Press any key to continue.
3.2、声明顺序对内存的影响
字节对齐要求,对
不同大小字节
的非静态数据成员的声明顺序有什么启发?例如:分别定义Point3D
与Point3D_Extend
类,两个类具有相关的非静态数据成员,但是声明顺序不一样,两个类对象占用的内存大小也不一样
#include <iostream>
using namespace std;
class Point3D
{
private:
int m_x;
short m_w;
int m_y;
short m_v;
};
class Point3D_Extend
{
private:
int m_x;
int m_y;
short m_w;
short m_v;
};
int main()
{
Point3D obj1;
Point3D_Extend obj2;
cout << "Point3D object size: " << sizeof(obj1) << endl;
cout << "Point3D_Extend object size: " <<sizeof(obj2) << endl;
return 0;
}
输出结果:
Point3D object size: 16
Point3D_Extend object size: 12
Process returned 0 (0x0) execution time : 0.241 s
Press any key to continue.
结论:相同大小的非静态数据成员放在一起,可以减少一个类对象内存占用
4、继承下的内存布局(非多态)
4.1、单继承内存布局
当把一个大的object赋值给小的object时,会引起object的切割,将大object的subobject赋值给小的object。C++保证出现在
derived class
中的base class subobject
有其完整原样性(即:派生类对象中有一个完整的基类对象)。例如:分别定义Point2d
与Point3d
类
#include <iostream>
using namespace std;
class Point2D
{
protected:
int m_x;
short m_y;
};
class Point3D : public Point2D
{
private:
short m_z;
};
int main()
{
Point2D *p1, *p2;
p1 = new Point2D;
p2 = new Point3D;
*p1 = *p2; // 大的对象赋值给小的对象时,会引起对象的切割
return 0;
}
4.2、多继承与多重继承的对象布局
多继承场景内存布局同单继承场景类似,如下:
5、继承下的内存布局(多态)
如果类中声明了虚函数,会给类对象创建一个虚函数指针,指向虚函数表。基类与派生类都有自己的虚函数指针、如果派生类不实现基类的虚函数,派生类的虚函数表相同索引位置存储的是基类的虚函数指针。