类的内存结构(一)

类的内存结构(一)

  • 类的内存结构包含什么?
    静态成员变量和静态成员函数是不会计算在类的内存结构中的,因为静态static决定了它们早在编译期就确定了静态变量区中的地址,因此通常来说类的内存结构只包含普通成员变量。
  • 还有什么特殊情况下不止包含普通成员变量呢?
    1. 继承:当子类继承父类时,父类的普通成员变量同样会存储在子类的内存结构之中
    2. 虚函数:子类继承父类时,如果父类存在虚函数(父类存在虚函数,作为继承类的子类必定也存在虚函数),那么此时父类中存在一个虚函数指针,指向存储虚函数的地址,虚函数指针位于类内存结构的顶部。
    3. 当virtual遇上继承:虚继承的存在,是用于面对菱形继承的问题,在这里假设将TrueBase称为一级父类,Base1、Base2称为二级父类,Son称为三级子类。如果Base1、Base2同时作为TrueBase类的子类,而son类继承了Base1和Base2类,比如动物,羊和驼,羊驼(切勿当真)。此时为了避免Son类中重复存在TrueBase类的成员变量,将Base1和Base2定义为TrueBase的虚拟继承子类(就像继承了但没有完全继承)。
  • 虚继承是什么原理呢?
    虚继承实际上是通过虚基类指针来寻找TrueBase类的地址,和虚函数指针一样,是通过指针来指向内存地址,而不是像普通继承那样直接存入地址,形成空间浪费。
  • 加入了继承、虚函数、虚继承之后,内存结构究竟变得怎么样了呢?
    (Base1: virtual public TrueBase; Base2: virtual Public TrueBase; Son: public Base1, public Base2)

    vfptr即虚函数指针,vbptr是虚基类指针,vfptr在内存中处于vbptr之前,且子类通过vbptr寻找的基类存储在最后。
  • 肯定有人问为什么当发生虚继承时,Base1和Base2的vfptr呢?Son的vbptr呢?
    1. 在macOS的GCC编译器Clion平台下,准确来说,是64位系统下的GCC编译器中,子类父类会共享一个虚函数指针!!!,所以虚函数指针只有一个!
    2. csdn中的推荐链接,给了我很大的启发和帮助,感谢作者:https://blog.csdn.net/longjialin93528/article/details/79874558
    3. 问vbptr的去好好面壁思过(笑),只有虚拟继承才会出现虚基类指针,此时Son只有public继承,是不会存在虚基部指针的。当然,如果是虚继承,那么自然会多出一个vbptr指针。
  • 内存对齐的问题
    需要注意的是,不同编译器下的不同数据类型的大小是不同的,在64位系统下,我的macOS,Clion,GCC编译器下指针是8字节。为了使内存最大程度地被利用而且规范,会存在内存对齐的要求。
    以最长的虚继承的Son类型为例,大小应该是vbptr(8)+int(8)+vbptr(8)+int(4)+int(4)+vfptr(8)+int(8)= 48。因为存在内存对齐,int这种4大小的类型,如果不能组合为8大小,将会由4扩至8,在Clion中编写,大小确实是48,证明推断并无错误。
  • 总结
    尽管类的内存结构很复杂,但是我们只要掌握这些最基本的结构知识,还是能够推断出大部分情况下的内存大小的。
    但是,不要为了所谓的学习,去故意写一些乱七八糟的继承,不仅从实际出发不实用,而且会让自己陷入陷阱中,在日常学习和编程中加以利用这些知识才是正途。

原文地址:https://www.cnblogs.com/RenjieZhao/archive/2022/12/02/16943283.html