为什么类C只包含B类型的成员变量,其sizeof(C)不等于sizeof(B)?

lei*_* hu 4 c++ memory inheritance struct sizeof

我写了三个类:ABCA没有成员变量,B有一个整型类型的成员变量,C有一个类型为 的成员变量B,如下所示:

class A {
public:

};

class B  : public A{
private:
    int value_;
};

class C : public A{
private:
    B a;
};
Run Code Online (Sandbox Code Playgroud)

让我惊讶的是,sizeof(C)正如预期的那样,是 8 而不是 4 sizeof(B)。我曾考虑过这可能是由于填充造成的,但是当我删除C继承自AB继承自时Asizeof(C)如预期的那样变成了 4。

当使用 GCC 和 Clang 时,我在 64 位机器上观察到相同的行为。

我想知道为什么编译器在编译时会额外增加4个字节的存储开销C

use*_*522 10

中有两个子A对象C:直接A基类子对象和成员子对象A的基类子对象B

该标准要求这两个对象不具有相同的地址,因为它们具有相同的类型和重叠的生存期,并且因为一个对象不嵌套在另一个对象中。(参见[intro.object]/9

因此,不可能对类进行布局,C以便直接A子对象和B成员子对象都分配在偏移量 0 处。

假设分配按顺序从直接A基类子对象的偏移量 0 处开始,那么也尊重对齐要求的最佳分配策略是将成员子对象放在偏移量 4 处,这是和B的对齐要求。因此总大小至少为 8。intB

从技术上讲,我认为标准也允许(忽略本问题末尾提出的问题)将B成员子对象布局在偏移处0,并将A成员子对象布局在除 之外的任何偏移处0,因为A基类子对象是零的子对象可能与部分子对象重叠的大小int。我认为也没有强制基类子对象的地址小于或等于B成员子对象的地址的排序要求。有了这样的选择,sizeof(C) == 4就有可能。但这将是一个令人惊讶的布局。

到目前为止,关于标准对布局的要求,但标准并没有指定编译器实际选择的具体布局。相反,它将由所使用的 C++-ABI 决定,如果您在 Linux 系统上使用 GCC 或 Clang 进行编译,则为Itanium C++ ABI 。此 ABI 使用的分配算法在此处指定(对于出于布局目的而不是 POD 的类,但事实C并非如此)。您可以在那里看到特殊情况:

将 D 放置在此偏移处,除非这样做会导致同一类型的两个组件(直接或间接)具有相同的偏移量。[...]

MSVC,遵循它自己的 C++-ABI,说sizeof(C) == 4并且通过我的测试似乎给出了直接A基类子对象以及B成员子对象 offset 0,使用不符合上述要求的不同分配策略。


不幸的是,该标准存在缺陷,导致无法正确布局C

一方面,如上所示,B必须放置在与直接A基子对象的偏移处,但是根据标准中的当前定义,它C是一个标准布局类,这意味着根据[basic.compound]/4,直接基子对象和A基类子对象以及B作为 的第一个非静态成员的成员子对象C必须放置在与C对象相同的地址处,即偏移量 0 处。

这是标准中的一个缺陷,(我猜)可能会通过使其C不是标准布局来解决。那么 MSCV 的 ABI 将仍然不符合要求,但这也可能使针对缺陷采用该解决方案变得困难。

请参阅开放的CWG 问题 2736

  • @nmcouldbeanAI `C` 对象可以与基类子对象进行指针互换,因为 `C` 是标准布局。这意味着相同的地址,即偏移量 0。请参阅 https://eel.is/c++draft/basic.compound#4(特别是项目符号 4.3) (2认同)