空类与虚继承的内存大小分析
空类与虚继承的内存大小分析一、C 中空类的大小规则核心规则C 标准规定任何对象都必须有唯一的地址因此空类的大小至少为1 字节。classA{};// 空类intmain(){coutsizeof(A)endl;// 输出 1大多数编译器return0;}为什么要占 1 字节保证两个不同的对象有不同的地址A a1, a2;需要a1 ! a2二、场景一普通继承无虚继承classA{};// 1 字节classB:publicA{};// 继承 AclassC:publicA{};// 继承 AclassD:publicB,publicC{};// 多重继承 B 和 Cintmain(){D d;coutsizeof(d)endl;return0;}内存布局分析三、场景二虚继承classA{};// 1 字节classB:virtualpublicA{};// 虚继承 AclassC:virtualpublicA{};// 虚继承 AclassD:publicB,publicC{};intmain(){D d;coutsizeof(d)endl;// 通常输出 8return0;}虚继承的内存布局分析四、为什么虚继承后大小是 8 字节关键点当虚基类 A 是空类时编译器会进行优化——空基类优化Empty Base Optimization, EBO。4.1 空基类优化EBOC 标准允许编译器对空基类进行优化不为空基类分配额外的存储空间。// 普通继承无虚继承时EBO 不适用// 实际上对于非虚继承的空基类编译器也会尝试优化classA{};classB:publicA{intx;};// sizeof(B) 4x 的大小A 不占用额外空间EBO 生效// 但对于多重继承且基类是空类时classD:publicB,publicC{};// B 和 C 都包含 A且 A 是空类// 如果不用虚继承会有两个 A 的占位符各 1 字节// 如果用虚继承A 只存在一份4.2 虚继承时的大小计算classA{};// 空类classB:virtualpublicA{};// 虚继承classC:virtualpublicA{};// 虚继承classD:publicB,publicC{};64 位系统下常见的输出编译器sizeof(D)说明GCC8两个 vbptr虚基类 A 被优化掉Clang8同上MSVC8同上8 字节的组成B::vbptr:4 字节32位或 8 字节64位C::vbptr4 字节32位或 8 字节64位A 部分被优化掉0 字节五、内存布局对比表类普通继承虚继承A1 字节占位符1 字节B1 字节继承 A 的占位符8 字节vbptrC1 字节继承 A 的占位符8 字节vbptrD2 字节两份 A8 字节两个 vbptrA 被共享六、为什么虚继承会引入 vbptr// 虚继承需要能够找到共享的虚基类// 因此需要在每个虚继承的子对象中存储一个指针vbptr// 这个指针指向虚基表表中记录了虚基类相对于当前对象的偏移classB:virtualpublicA{// 编译器添加// void* __vbptr; // 虚基类指针8 字节64位};classC:virtualpublicA{// void* __vbptr; // 8 字节};classD:publicB,publicC{// 包含 B::__vbptr (8) C::__vbptr (8)// 虚基类 A 被放在对象末尾但因为是空类被优化掉// 总大小 16 字节// 实际上只有 8 字节说明编译器做了更激进的优化};七、总结继承方式内存大小原因空类 A1 字节保证对象有唯一地址普通继承单继承1 字节EBO 优化不额外占空间普通继承多重继承2 字节两份 A 的副本虚继承单继承8 字节64位存储 vbptr虚继承多重继承8 字节64位两个 vbptrA 共享且被优化