侧边栏壁纸
博主头像
赵东阳的个人网站

行动起来,活在当下

  • 累计撰写 20 篇文章
  • 累计创建 8 个标签
  • 累计收到 1 条评论

目 录CONTENT

文章目录
C++

c++对象模型

温馨提示:
部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

c++对象布局总览(1)

​ 虚函数、非虚函数、非静态成员变量、静态成员变量,这四种对一个类实例化出的对象的大小的影响,一个实例对象中包含非静态数据成员,虚表指针,以及为对齐而必须的填充,静态成员变量、函数独立于单个实例化对象而存在。

​ c++ 规定,空类对象的大小至少为一个字节,只是为了区分实例化对象,例如创建了多个空类的对象,可以通过对象的内存地址区分。{假如一个类中没有变量,只存在普通成员函数,没有虚函数,那,它的对象可不就是一个空类么。}128743716471463422

图中存在字节对齐的填充,我们可以通过设置编译的宏,来改变对齐的规则。

c++ 对象模型总览(2)

C++ 标准规定,非对象的数据在访问相同的控制层级的情况下,先声明的对象分配较低的内存地址。15729716471472592

那么上述图片中的情况,具体又是如何实现的呢?

  1. 函数成员不会影响对象的内存布局。
  2. 同时存在Pubilc和Private两个访问控制级别时,如何处理呢,c++的标准种没有指定,允许编译器有不同的实现,但是实际上,编译器是按照顺序来安排内存的。(可能存在的字节填充而不会相邻)。

image-20220313222058576

虽然上图中两个对象的内存占用仅仅差了4个字节,但是,当用户量足够大时,节省的资源是极为可观的。

对象模型总览(3)

公有继承下的对象布局(非多态)

1.单继承的对象布局

image-20220313223702423

会造成两个填充,而不是由int z补上。因为我们可能会使用基类指针来指向派生类的对象,我们不希望这个时候产生问题。即:当把一个大的object复制给一个“小”的object时,会引起object的切割。

“c++语言保证,‘出现在的derived class 中的base class subobject有其完整原样性’ ”。——《深度探索C++对象模型》  

小的object可以给大的object么?

2.多继承的对象布局

image-20220313230127550

代码——>类图——>内存布局

我们发现W被继承了两份,除非我们使用虚拟继承。

3.多重继承的对象布局

image-20220313230643575

对于c++,多态是最常用的模型、一个父类的指针或者引用可以指向父类的对象,也可以指向子类的对象。当使用指针调用虚函数的时候,会根据具体指向的类型来决定调用哪一个虚函数。

多态在C++,子类的析构函数作用之前会调用父类的析构函数。?

上图中我们可以观察到,对象的指针直接指向了虚函数表vptr[0],而对象的内存中还存了一项Point2D_type_info,如果我们要调用这一项,需要使用vptr[-1];

而且析构函数竟然有两个,除了我们自己写的虚构函数以外,编译器还帮我们合成了一个。 

如果Point2d的析构函数不是一个虚函数,当父类的指针指向子类,调用析构函数去析构Point3D时,会直接调用父类的函数,而不会去调用point3D的析构函数。这时可能会引起内存的泄露。

析构函数和构造函数

在使用构造函数和析构函数时,需要特别注意对它们的调用时间和调用顺序。在一般情况下,调用析构函数的次序正好与调用构造函数的次序相反:最先被调用的构造函数,其对应的(同一对象中的)析构函数最后被调用,而最后被调用的构造函数,其对应的析构函数最先被调用。
可以简记为:先构造的后析构,后构造的先析构,它相当于一个栈,先进后出。

下面归纳一下什么时候调用构造函数和析构函数:

  1. 在全局范围中定义的对象(即在所有函数之外定义的对象),它的构造函数在文件中的所有函数(包括main函数)执行之前调用。但如果一个程序中有多个文件,而不同的文件中都定义了全局对象,则这些对象的构造函数的执行顺序是不确定的。当main函数执行完毕或调用exit函数时(此时程序终止),调用析构函数。

  2. 如果定义的是局部自动对象(例如在函数中定义对象),则在建立对象时调用其构造函数。如果函数被多次调用,则在每次建立对象时都要调用构造函数。在函数调用结束、对象释放时先调用析构函数。

  3. 如果在函数中定义静态(static )局部对象,则只在程序第一次调用此函数建立对象时调用构造函数一次,在调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用析构函数

继承关系的构造函数和析构函数的执行顺序为

1、父类构造函数执行。

2、子类构造函数执行。

3、子类析构函数执行。

4、父类析构函数执行。

组合关系的构造函数和析构函数执行顺序为:

1、执行类成员对象的构造函数。

2、执行类自己的构造函数。

3、执行类自己的析构函数。

4、执行类成员的析构函数。

0

评论区