对象布局

关于 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)继承布局

虚拟继承的数据放在具体继承基类和非静态数据的后面:

  1. vptr:如果可能,从基类列表中选择第一个具体继承的、带有 vptr 的基类,将它提到最前面,保持其他基类的次序不变。这样该基类和子类的 vptr 可以共享,从而对象直接由基类开始,没有额外的 vptr
    1. 虚拟继承的基类不会被考虑 vptr 共享,因为它们始终被放在对象的最后位置。
    2. 被选择的基类的 vptr 可以是因为虚拟继承得到的(即可以不是多态类)。
    3. 如果需要 vptr 但又不能共享,则在开头添加额外的 vptr
  2. 具体继承基类子对象
  3. 本类非静态数据
  4. 虚基类子对象

第一个基类子对象的 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
}