在 C++ 中,为啥只包含联合及其基类实例的派生类占用的内存比联合的大小还要多?

Posted

技术标签:

【中文标题】在 C++ 中,为啥只包含联合及其基类实例的派生类占用的内存比联合的大小还要多?【英文标题】: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?在 C++ 中,为什么只包含联合及其基类实例的派生类占用的内存比联合的大小还要多? 【发布时间】:2018-05-12 19:21:47 【问题描述】:

更具体地说,从空类继承的类仅包含一个联合,其成员包括基本无数据类的实例,它占用的内存比联合多。为什么会发生这种情况,有什么办法可以避免消耗额外的内存?

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

#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 编译时,此代码的输出是:

1
16
12

我希望类 bigsmall 应该具有相同的大小;然而奇怪的是,bigsmall 占用更多的内存,尽管它们似乎都包含相同的数据。

同样即使union中数组的大小发生变化,bigsmall的大小之差也是恒定的4个字节。

-编辑:

似乎这种行为并非特定于具有联合数据类型的类。类似的行为发生在派生类具有基类类型的成员的其他类似情况下。感谢指出这一点的人。

【问题讨论】:

这与工会无关。 【参考方案1】:

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

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

空基类优化基于将空基类的“存储”与其他类型混叠。通常,这是通过为其提供与父类相同的地址来完成的,这意味着该地址通常与第一个非空基类或第一个成员子对象相同。

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

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

【讨论】:

【参考方案2】:

union 在这里是一个红鲱鱼。

如果你简化为

struct empty;

struct big : empty

    empty e;
;

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

struct small : empty

    int n;
;

您可以预期sizeof(small)sizeof(int)

【讨论】:

以上是关于在 C++ 中,为啥只包含联合及其基类实例的派生类占用的内存比联合的大小还要多?的主要内容,如果未能解决你的问题,请参考以下文章

C++ 派生模板类:访问实例的受保护成员

C++ 包含两个派生自同一个基类的类

关于C++基类、派生类的引用和指针

C++ 为啥我可以从派生类调用基类的私有虚函数?

为啥以及何时使用多态性将基类指向 C++ 中的派生类 [重复]

从设计基类及其派生类看继承关系