C++ 对象模型 | 关于对象

一、C++ 对象模型

1、对象内存布局

在C++中,有两种数据成员:staticnonstatic,以及三种成员方法staticnonstaticvirtual,下面从虚函数、非虚函数、静态成员变量、非静态成员变量等维度来分析,类对象的内存布局。例如:下面定义一个Point类,包含前面四种类型的成员

#include <iostream>
using namespace std;class Point
{
public:Point(double val);virtual ~Point();         // virtual function numbersdouble GetPoint();        // notstatic function numbersstatic int PointCount();  // static function numbers
private:double m_x;           // notstatic data numbersstatic 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;  // 输出结果:16return 0;
}

一个实例对象中包含非静态数据成员虚表指针以及为对齐而必需的填充静态成员变量函数独立于单个实例化对象。

总结:影响C++对象大小的三个因素:非静态数据成员虚函数字节对齐

2、空对象

C++规定空类对象大小至少为1字节,只是为了区分实例化对象。如果创建了多个空类的对象,可以通过对象的内存地址区分。例如:下面创建一个空类Empty

#include <iostream>
using namespace std;class Empty
{
};int main()
{Empty e;cout << sizeof(e) << endl;  // 输出结果:1return 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 0x61fe1cProcess returned 0 (0x0)   execution time : 0.241 s
Press any key to continue.

3.2、声明顺序对内存的影响

字节对齐要求,对不同大小字节的非静态数据成员的声明顺序有什么启发?例如:分别定义Point3DPoint3D_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: 12Process 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有其完整原样性(即:派生类对象中有一个完整的基类对象)。例如:分别定义Point2dPoint3d

#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、继承下的内存布局(多态)

如果类中声明了虚函数,会给类对象创建一个虚函数指针,指向虚函数表。基类与派生类都有自己的虚函数指针、如果派生类不实现基类的虚函数,派生类的虚函数表相同索引位置存储的是基类的虚函数指针。