在C++中,为什么只包含与其基类实例的并集的派生类占用的内存大于并集的大小?

Dan*_*l H 7 c++ inheritance class sizeof

更具体地说,一个继承自空类的类,只包含一个union,其成员包含一个基本无数据类的实例,占用的内存比union更多.为什么会发生这种情况,有没有办法避免花费额外的内存?

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

#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;
}
Run Code Online (Sandbox Code Playgroud)

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

1
16
12
Run Code Online (Sandbox Code Playgroud)

我期望类的大小的应该是相同的大小; 然而奇怪的是,即使它们两者看起来都包含相同的数据,大的内存也比小的内存多.

也即使在工会阵列的大小被改变,大小之间的差是常数4个字节.

-编辑:

看起来这种行为并不特定于具有联合数据类型的类.类似的行为发生在派生类具有基类类型的成员的其他类似情况中.感谢那些指出这一点的人.

Nic*_*las 12

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

big包含两个注意的子对象:基类empty_class和包含成员的匿名联合empty_class.

空基本优化基于对具有其他类型的空基类的"存储"进行别名.通常,这是通过为其提供与父类相同的地址来完成的,这意味着该地址通常与第一个非空基或第一个成员子对象相同.

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

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


Bat*_*eba 10

union是一个红鲱鱼.

如果你简化到

struct empty{};

struct big : empty
{
    empty e;
};
Run Code Online (Sandbox Code Playgroud)

然后sizeof(big) 必须大于sizeof(empty).这是因为有两类对象emptybig,因此它们需要不同的地址.该空基地优化在这里不能应用,因为它可能是

struct small : empty
{
    int n;
};
Run Code Online (Sandbox Code Playgroud)

在那里你可以期望sizeof(small)sizeof(int).