【问题标题】:Difference between class and struct with regards to padding and inheritance类和结构在填充和继承方面的区别
【发布时间】:2019-07-19 14:04:42
【问题描述】:

以下所有操作都将在 GCC 9.1 上使用 Compiler Explorer 在 x86-64 中使用 -O3 完成。

我有这个代码:

struct Base {
    Base() {}
    double foo;
    int bar;
};

struct Derived : public Base {
    int baz;
};

int main(int argc, char** argv)
{
    return sizeof(Derived);
}

https://godbolt.org/z/OjSCZB

它正确返回16,正如我所料,foo 8 个字节,bar 4 个字节和baz 4 个字节。这只是因为Derived 继承自Base,因此它不必在bar 之后填充,因为Derived 是包含BaseDerived 元素的单一类型。

我有两个问题,如下:

第一个问题

如果我删除Base() {} 的显式构造函数,它开始返回24,而不是16。即它在barbaz 之后添加填充。

https://godbolt.org/z/0gaN5h

我无法解释为什么拥有显式默认构造函数与拥有隐式默认构造函数有什么不同。

第二个问题

如果我随后将 Basestruct 更改为 class,它会变回返回 16。我也无法解释这一点。为什么访问修饰符会改变结构的大小?

https://godbolt.org/z/SCYKwL

【问题讨论】:

  • 半个 stackoverflow.com/q/47914612/560648 的欺骗。我不知道为什么更改引入 base 的关键字会有所不同(鉴于无论如何都不会进行成员重新排序,因此不应该涉及不同的成员访问级别)
  • 啊,谢谢!这回答了第一个问题,但不是第二个
  • 回复:“它正确地返回 16,正如我所期望的那样”——根据定义,sizeof 的结果是正确,不管你期望什么。跨度>
  • 嗯,是的,它是访问说明符;添加public:
  • @LightnessRacesinOrbit 不是错误。聚合不能有私有成员

标签: c++ c++11 gcc


【解决方案1】:

这一切都归结为您的类型是否为聚合。与

struct Base {
    Base() {}
    double foo;
    int bar;
};

struct Derived : public Base {
    int baz;
};

Base 不是因为构造函数的聚合。当您删除构造函数时,您使 Base 成为一个聚合,根据 Adding a default constructor to a base class changes sizeof() a derived type,这意味着 gcc 不会“优化”空间并且派生对象不会使用基础的尾部填充。

当你把代码改成

class Base {
    double foo;
    int bar;
};

struct Derived : public Base {
    int baz;
};

foobar 现在是私有的(因为类默认具有私有可访问性),这再次意味着 Base 不再是聚合,因为聚合不允许有私有成员。这意味着我们回到了第一个案例的工作原理。

【讨论】:

    【解决方案2】:

    使用您的 Base 类,您将获得 4 个字节的尾部填充,与 Derived 类相同,这就是为什么它通常应该是 24 bytes 总计 Derived 的大小。

    它变成了 16 个字节,因为你的编译器能够做到tail padding reuse

    但是,POD types(所有成员公共、默认构造函数等)的尾部填充重用存在问题,因为它打破了程序员会做出的常见假设。 (所以基本上任何理智的编译器都不会对 pod 类型进行尾部填充重用)

    让我们假设编译器将 tail padding reuse 用于 POD 类型:

    struct Base {
        double foo;
        int bar;
    };
    
    struct Derived : Base {
        int baz;
    };
    
    int main(int argc, char** argv)
    {
        // if your compiler would reuse the tail padding then the sizes would be:
        // sizeof(Base) == 16
        // sizeof(Derived) == 16
    
        Derived d;
        d.baz = 12;
        // trying to zero *only* the members of the base class,
        // but this would zero also baz from derived, not very intuitive
        memset((Base*)&d, 0, sizeof(Base));
    
        printf("%d", d.baz); // d.baz would now be 0!
    }
    
    

    向 Base 类添加显式构造函数时,或通过将 struct 关键字更改为 classDerived 类不再满足 POD 定义,因此不会发生尾部填充重用。

    【讨论】:

      猜你喜欢
      • 2023-03-13
      • 1970-01-01
      • 1970-01-01
      • 2023-04-09
      • 2015-09-04
      • 1970-01-01
      • 2010-10-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多