在 C++ 中,为什么仅包含一个联合及其基 class 实例的派生 class 占用的内存多于联合的大小?

In C++, why does a derived class that just contains a union with an instance of its base class take more memory than the size of the union?

更具体地说,一个 class,继承自一个空 class,只包含一个联合,其成员包括基本无数据 class 的实例,占用更多内存不仅仅是工会。为什么会发生这种情况,是否有任何方法可以避免消耗额外的内存?

下面的代码说明了我的问题:

#include <iostream>

class empty_class { };

struct big : public empty_class
{
    union
    {
        int data[3];
        empty_class a;
    };
};

struct small
{
    union
    {
        int data[3];
        empty_class a;
    };
};

int main()
{   
    std::cout << sizeof(empty_class) << std::endl;
    std::cout << sizeof(big)         << std::endl;
    std::cout << sizeof(small)       << std::endl;
}

此代码的输出,当使用 gcc 7.3.0 版编译时 -std=c++17(尽管我使用 c++11 和 c++14 得到相同的结果)是:

1
16
12

我希望 classes bigsmall 的大小应该相同;然而奇怪的是,bigsmall 占用更多的内存,尽管它们看起来包含相同的数据。

同样即使union中数组的大小发生变化,bigsmall的大小相差一个常量 4 个字节。

-编辑:

似乎此行为并非特定于具有联合数据类型的 classes。在派生 class 具有基本 class 类型的成员的其他类似情况下,也会出现类似的行为。感谢指出这一点的人。

这是因为我称之为 C++ 的 "unique identity rule"。 C++ 中特定类型 T 中的每个(活动)对象必须始终 具有与类型 T 的每个其他活动对象不同的地址。编译器无法为违反此规则的类型提供布局,其中具有相同类型 T 的两个不同子对象在其包含对象的布局中具有相同的偏移量。

Class big 包含两个值得注意的子对象:基 class empty_class 和包含成员 empty_class 的匿名联合。

空基数优化基于将空基数 class 的 "storage" 与其他类型别名化。通常,这是通过为它提供与父对象相同的地址 class 来完成的,这意味着该地址通常与第一个非空基或第一个成员子对象相同。

如果编译器为基 class empty_class 提供了与联合成员相同的地址,那么您将拥有 class 的两个不同的子对象(big::empty_classbig::a) 具有相同的地址但不同的对象。

这样的布局会违反唯一身份规则。因此,编译器不能在这里使用空基优化。这也是 big 不是标准布局的原因。

union 在这里是一条红鲱鱼。

如果简化为

struct empty{};

struct big : empty
{
    empty e;
};

sizeof(big)必须大于sizeof(empty)。这是因为 big 中有两个 empty 类型的对象,因此它们需要不同的地址。 空基优化不能在这里应用,因为它可以用于

struct small : empty
{
    int n;
};

您可以期望 sizeof(small) 成为 sizeof(int)