【问题标题】:why sizeof(Base) is not different of sizeof(Derived)为什么 sizeof(Base) 与 sizeof(Derived) 没有什么不同
【发布时间】:2019-08-17 22:42:53
【问题描述】:

我觉得sizeof(Base)应该是12,为什么是16?

没有虚函数,我得到 4 和 8。

class Base{
  public:
    int i;
    virtual void Print(){cout<<"Base Print";}
};

class Derived:public Base{
  public:
    int n;
    virtual void Print(){cout<<"Derived Print";}
};

int main(){
  Derived d;
  cout<<sizeof(Base)<<","<<sizeof(d);
  return 0;
}

预期结果:12,16

实际结果:16,16

【问题讨论】:

  • 可能是填充问题?

标签: c++ sizeof virtual-functions memory-layout vptr


【解决方案1】:

这是因为编译器决定对齐你的类。

如果您希望(或需要)结构或类具有“真实”大小,您可以像这样使用#pragma pack(1)

#pragma pack(push, 1) // Set packing to 1 byte and push old packing value to stack

class Base{
  public:
    int i;
    virtual void Print(){cout<<"Base Print";}
};

class Derived:public Base{
  public:
    int n;
    virtual void Print(){cout<<"Derived Print";}
};

int main(){
  Derived d;
  cout<<sizeof(Base)<<","<<sizeof(d);
  return 0;
}

#pragma pack(pop) // restore old packing value

【讨论】:

  • 请注意,除非您有非常充分的理由绕过对齐规则,否则您可能不应该这样做。
  • 谢谢,对我很有帮助。
  • 注: #pragma pack 是编译器扩展。
【解决方案2】:

为什么 sizeof(Base) 与 sizeof(Derived) 不同

因为编译器引入了对齐

这是架构相关的,但为了简单起见,我假设我们指的是 64 位架构。

Scenario 64 bit / Clang 8.0.

Base 类型的对齐方式是8 字节:

alignOfBase(): # @alignOfBase()
  mov eax, 8
  ret

Base的布局由变量成员(int)和虚拟表(vtptr)组成。

如果我们假设一个“通用”架构,其中:

  • int 是 4 字节大小。
  • vtptr 是一个指针。在 64 位架构上是 8 字节大小。

如您所料,我们应该有一个 4 + 8 = 12 的总和。

但是,我们需要记住Base 的对齐方式是8 bytes。因此,连续的Base 类型应该存储在 8 的位置倍数中。

为了保证这一点,编译器为Base 引入了填充。这就是Base 大小为 16 字节的原因。

例如,如果我们考虑 2 个连续的 Basebase0base1)没有填充:

0:  vtptr (base 0) + 8
8:  int   (base 0) + 4
12: vtptr (base 1) + 8  <--- Wrong! The address 12 is not multiple of 8.
20: int   (base 1) + 4

带内边距:

0:  vtptr (base 0) + 8
8:  int   (base 0) + 4+4   (4 padding)
16: vtptr (base 1) +8      <--- Fine! The adress 16 is multiple of 8.
24: int   (base 1) +4+4    (4 padding)

Derived 类型也是如此。

Derived的布局应该是:vtptr + int + int,即8 + 4 + 4 = 16

Derived 的对齐方式也是8

alignOfDerived(): # @alignOfDerived()
  mov eax, 8
  ret

确实,在这种情况下,不需要引入填充来保持Derived 与内存对齐。布局大小将与实际大小相同。

0:   vtptr (Derived 0)
8:   int   (Derived 0)
12:  int   (Derived 0)
16:  vtptr (Derived 1)  <---- Fine. 16 is multiple of 8.
24:  int   (Derived 1)
28:  int   (Derived 1)

【讨论】:

  • 你忘了解释数组和结构之间的区别:struct { T x1, x2; };(原则上)可以有填充,但T x[2] 根据定义不能,所以任何可以在数组中的东西(任何具体对象类型)的大小必须与其对齐方式兼容。
猜你喜欢
  • 2012-06-14
  • 2021-12-17
  • 1970-01-01
  • 2015-08-21
  • 2013-09-24
  • 2012-01-10
  • 1970-01-01
  • 2021-05-12
  • 1970-01-01
相关资源
最近更新 更多