c++对象布局总览(1):
虚函数、非虚函数、非静态成员变量、静态成员变量,这四种对一个类实例化出的对象的大小的影响,一个实例对象中包含非静态数据成员,虚表指针,以及为对齐而必须的填充,静态成员变量、函数独立于单个实例化对象而存在。
c++ 规定,空类对象的大小至少为一个字节,只是为了区分实例化对象,例如创建了多个空类的对象,可以通过对象的内存地址区分。{假如一个类中没有变量,只存在普通成员函数,没有虚函数,那,它的对象可不就是一个空类么。}
图中存在字节对齐的填充,我们可以通过设置编译的宏,来改变对齐的规则。
c++ 对象模型总览(2)
C++ 标准规定,非对象的数据在访问相同的控制层级的情况下,先声明的对象分配较低的内存地址。
那么上述图片中的情况,具体又是如何实现的呢?
- 函数成员不会影响对象的内存布局。
- 同时存在Pubilc和Private两个访问控制级别时,如何处理呢,c++的标准种没有指定,允许编译器有不同的实现,但是实际上,编译器是按照顺序来安排内存的。(可能存在的字节填充而不会相邻)。

虽然上图中两个对象的内存占用仅仅差了4个字节,但是,当用户量足够大时,节省的资源是极为可观的。
对象模型总览(3)
公有继承下的对象布局(非多态)
1.单继承的对象布局

会造成两个填充,而不是由int z补上。因为我们可能会使用基类指针来指向派生类的对象,我们不希望这个时候产生问题。即:当把一个大的object复制给一个“小”的object时,会引起object的切割。
“c++语言保证,‘出现在的derived class 中的base class subobject有其完整原样性’ ”。——《深度探索C++对象模型》
小的object可以给大的object么?
2.多继承的对象布局

代码——>类图——>内存布局
我们发现W被继承了两份,除非我们使用虚拟继承。
3.多重继承的对象布局

对于c++,多态是最常用的模型、一个父类的指针或者引用可以指向父类的对象,也可以指向子类的对象。当使用指针调用虚函数的时候,会根据具体指向的类型来决定调用哪一个虚函数。
多态在C++,子类的析构函数作用之前会调用父类的析构函数。?
上图中我们可以观察到,对象的指针直接指向了虚函数表vptr[0],而对象的内存中还存了一项Point2D_type_info,如果我们要调用这一项,需要使用vptr[-1];
而且析构函数竟然有两个,除了我们自己写的虚构函数以外,编译器还帮我们合成了一个。
如果Point2d的析构函数不是一个虚函数,当父类的指针指向子类,调用析构函数去析构Point3D时,会直接调用父类的函数,而不会去调用point3D的析构函数。这时可能会引起内存的泄露。
析构函数和构造函数
在使用构造函数和析构函数时,需要特别注意对它们的调用时间和调用顺序。在一般情况下,调用析构函数的次序正好与调用构造函数的次序相反:最先被调用的构造函数,其对应的(同一对象中的)析构函数最后被调用,而最后被调用的构造函数,其对应的析构函数最先被调用。
可以简记为:先构造的后析构,后构造的先析构,它相当于一个栈,先进后出。
下面归纳一下什么时候调用构造函数和析构函数:
-
在全局范围中定义的对象(即在所有函数之外定义的对象),它的构造函数在文件中的所有函数(包括main函数)执行之前调用。但如果一个程序中有多个文件,而不同的文件中都定义了全局对象,则这些对象的构造函数的执行顺序是不确定的。当main函数执行完毕或调用exit函数时(此时程序终止),调用析构函数。
-
如果定义的是局部自动对象(例如在函数中定义对象),则在建立对象时调用其构造函数。如果函数被多次调用,则在每次建立对象时都要调用构造函数。在函数调用结束、对象释放时先调用析构函数。
-
如果在函数中定义静态(static )局部对象,则只在程序第一次调用此函数建立对象时调用构造函数一次,在调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用析构函数
继承关系的构造函数和析构函数的执行顺序为:
1、父类构造函数执行。
2、子类构造函数执行。
3、子类析构函数执行。
4、父类析构函数执行。
组合关系的构造函数和析构函数执行顺序为:
1、执行类成员对象的构造函数。
2、执行类自己的构造函数。
3、执行类自己的析构函数。
4、执行类成员的析构函数。
评论区