【问题标题】:Understanding the size of virtual derived class了解虚拟派生类的大小
【发布时间】:2021-04-14 06:29:28
【问题描述】:
#include <iostream>
using namespace std;
class A {
  int a;
};
class B1 : virtual public A {
  int b1;
};
class B2 : virtual public A {
  int b2;
};
class C : public B1, public B2 {
  int c;
};

int main() {
  A obj1; B1 obj2; B2 obj3; C obj4;
  cout << sizeof(obj1) << endl;
  cout << sizeof(obj2) << endl;
  cout << sizeof(obj3) << endl;
  cout << sizeof(obj4) << endl;
  return 0;
}

输出:

4
16
16
40

在上面的c++程序中, A 的大小为 4,因为它只有一个 int B1 的大小为 16,因为 (int+ int + virtual pointer) B2 相同

但是C的大小怎么是40???

【问题讨论】:

  • 可以有对齐和填充
  • @drescherjm 一切都是int。有什么要对齐或填充的?
  • @Spencer 可能这是 x64 架构,所以vptr 是 8 个字节。
  • @Spencer vptr 可能是 8 个字节。
  • 40 = 16 + 16 + sizeof(int) + 填充为 8 的倍数以满足指针的对齐要求。

标签: c++ multiple-inheritance virtual-inheritance memory-layout


【解决方案1】:

免责声明:C++ 标准没有规定这些,除了允许编译器添加填充,而且我实际上没有检查任何汇编代码。

如果在 x64 架构的 GCC 中编译,结构可能如下所示:

+-----------------------------------------------------------------------------------------------------+
| C                                                                                                   |
| +------------------------------------+ +------------------------------------+          +----------+ |
| | B1 (16)                            | | B2 (16)                            |          | A(4)     | |
| | +--------------+ +------+ +------+ | | +--------------+ +------+ +------+ | +------+ | +------+ | |
| | |  vptr (8)    | | b1(4)| |pad(4)| | | |  vptr (8)    | | b2(4)| |pad(4)| | | c(4) | | | a(4) | | |
| | +--------------+ +------+ +------+ | | +--------------+ +------+ +------+ | +------+ | +------+ | |
| +------------------------------------+ +------------------------------------+          +----------+ |
+-----------------------------------------------------------------------------------------------------+

检查每个结构(https://wandbox.org/permlink/a4l99p6JwTrSdWx1)的对齐方式,我们看到A 对齐到 4 个字节,而使用 GCC 编译器时,所有剩余的结构都对齐到 8 个字节。

我假设您的计算机采用 x64 架构。这意味着指针必须至少有 8 个字节长,并且因为 vptr 存在于 B1B2 中,所以整个结构对齐到 8 个字节。因此,编译器需要为这两个结构添加填充,以保持sizeof(B1) % alignof(B1) == 0(或简单地说保持结构对齐)。

【讨论】:

    【解决方案2】:

    根据您使用的编译器和系统架构,结果可能会有所不同。例如,msvc 19.28 x64 给出了这个不言自明的结果(使用选项/d1reportAllClassLayout 得到它):

    class C size(44):
        +---
     0  | +--- (base class B1)
     0  | | {vbptr}
     8  | | b1
        | | <alignment member> (size=4)
        | | <alignment member> (size=4)
        | +---
    16  | +--- (base class B2)
    16  | | {vbptr}
    24  | | b2
        | | <alignment member> (size=4)
        | | <alignment member> (size=4)
        | +---
    32  | c
        | <alignment member> (size=4)
        +---
        +--- (virtual base A)
    40  | a
        +---
    

    但对于msvc 19.28 x86,结果将是:

    class C size(24):
        +---
     0  | +--- (base class B1)
     0  | | {vbptr}
     4  | | b1
        | +---
     8  | +--- (base class B2)
     8  | | {vbptr}
    12  | | b2
        | +---
    16  | c
        +---
        +--- (virtual base A)
    20  | a
        +---
    

    更新

    需要注意的是,上面的类布局清楚地展示了虚拟继承的特性,其中只有一个基类实例(A)的副本被孙派生类(C)继承。如果A::a 数据成员是公开的,我们可以在C 类的成员函数中使用以下语句:

    void C::foo() {
        B2::a = 1;
        B1::a = 2;
        std::cout << B2::a << " " << B1::a << " " << A::a; // Output: 2 2 2
    }
    

    但是如果你不使用虚拟继承(而只是公共的),那么C类的布局会是这样的:

    class C size(20):
        +---
     0  | +--- (base class B1)
     0  | | +--- (base class A)
     0  | | | a
        | | +---
     4  | | b1
        | +---
     8  | +--- (base class B2)
     8  | | +--- (base class A)
     8  | | | a
        | | +---
    12  | | b2
        | +---
    16  | c
        +---
    

    我们将拥有基类A的成员变量的两个副本

    void C::foo() {
        B2::a = 1;
        B1::a = 2;
        std::cout << B2::a << " " << B1::a; // Output: 1 2. `A::a` ambiguity error
    }
    

    【讨论】:

      【解决方案3】:

      知道C 的确切布局和填充方式并不是很重要,因为这是实现定义的,并且在平台之间会有所不同。但是有一个具体的细节......

      基类A,被虚拟继承,被提取到层次结构的顶部。因此,只要“单独”使用B1,它就是B1 的一部分。当它在C 内部进一步继承时,A 不再是它的一部分。

      这意味着我们不能依赖sizeof(B1)sizeof(B2),因为我们必须先从它们中减去sizeof(A)。但是从B1 中删除 4 个字节不会将其大小从 16 减少到 12,因为由于 vbptr,它必须对齐并填充到 8 个字节(假设为 64 位)。

      所以我们最终得到sizeof(B1 less A)+sizeof(B2 less A)+sizeof(A)+sizeof(C::c) = 16+16+4+4 = 40。

      如果我们将A 放大,我们可以获得一些时髦的输出:

      #include <iostream>
      using namespace std;
      class A {
        int a1,a2,a3,a4,a5;
      };
      class B1 : virtual public A {
        int b1;
      };
      class B2 : virtual public A {
        int b2;
      };
      class C : public B1, public B2 {
        int c;
      };
      
      int main() {
        A obj1; B1 obj2; B2 obj3; C obj4;
        cout << "sizeof(A)  = " << sizeof(obj1) << endl;
        cout << "sizeof(B1) = " << sizeof(obj2) << endl;
        cout << "sizeof(B2) = " << sizeof(obj3) << endl;
        cout << "sizeof(C)  = " << sizeof(obj4) << endl;
        return 0;
      }
      

      Prints:

      sizeof(A)  = 20
      sizeof(B1) = 32
      sizeof(B2) = 32
      sizeof(C)  = 56
      

      现在更清楚sizeof(C) sizeof(B1) + sizeof(B2)

      要了解更多信息,您可以play with offsets 了解C 的确切布局。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-02-26
        • 2020-09-30
        • 2020-02-01
        • 1970-01-01
        • 1970-01-01
        • 2019-11-04
        • 2011-01-15
        相关资源
        最近更新 更多