对象布局
关于 access section
不同 access section 数据不保证按序布局。
我在 Compiler Explorer 上测试了 gcc 和 clang,他们都是忽略 access 权限,将各个 access section 的变量布局直接拼接在一起的。
注:同一个权限也可以是不同的 access section:
具体(concrete)继承布局
基类对象直接嵌入到子类中。如果基类中有 padding,默认布局下也不会利用。
struct A1 {
int x;
virtual void f() {} // vptr 加上 int 值,本来只有 12 个字节但还有 4 字节的 padding
};
// 如果把 y 放在 A1 的 padding 的位置,就只需要 24 字节,但很遗憾不能这么布局
struct B1: virtual A1 { int y; };
static_assert(sizeof(A1) == 16);
static_assert(sizeof(B1) == 32);
虚拟(virtual)继承布局
虚拟继承的数据放在具体继承基类和非静态数据的后面:
- vptr:如果可能,从基类列表中选择第一个具体继承的、带有 vptr 的基类,将它提到最前面,保持其他基类的次序不变。这样该基类和子类的 vptr 可以共享,从而对象直接由基类开始,没有额外的 vptr。
- 虚拟继承的基类不会被考虑 vptr 共享,因为它们始终被放在对象的最后位置。
- 被选择的基类的 vptr 可以是因为虚拟继承得到的(即可以不是多态类)。
- 如果需要 vptr 但又不能共享,则在开头添加额外的 vptr。
- 具体继承基类子对象
- 本类非静态数据
- 虚基类子对象
第一个基类子对象的 vptr 和本身的 vptr 尽可能重合(相当于不额外增加 vptr)。多继承的后续基类子对象要单独存储 vptr。
下面的 Cat 的 vptr 和 Garfield 的 vptr 重合。
改变继承的先后顺序可以节省空间:
下面的代码能印证上面的推理:
#include <cstdint>
#include <cstdio>
struct A {
uint64_t x;
};
struct B {
int b;
virtual void foo() {}
};
struct C {
int c;
virtual void foo() {}
};
struct D : A, B, C {
uint64_t y;
void foo() override {}
};
int main() {
D *p = new D;
A *q = p;
B *r = p;
C *t = p;
// printf 打印指针有没有 0x 前缀得看实现
// std::cout 打印指针会增加 0x 前缀而且默认不打印前导零
printf("%p\n", p); // 0000020128496730
printf("%p\n", q); // 0000020128496740
printf("%p\n", r); // 0000020128496730
printf("%p\n", t); // 0000020128496748
}