在GCC 9.1上,使用-O3
在x86-64中使用Compiler Explorer完成以下所有操作。
我有此代码:
struct Base {
Base() {}
double foo;
int bar;
};
struct Derived : public Base {
int baz;
};
int main(int argc, char** argv)
{
return sizeof(Derived);
}
正如我期望的那样,它正确返回16
,foo
为8个字节,bar
为4个字节,baz
为4个字节。这仅是因为Derived
继承自Base
,所以由于bar
是同时包含Derived
和{ {1}}个元素。
我有两个问题,如下所示:
第一个问题
如果删除Base
的显式构造函数,它将开始返回Derived
,而不是Base() {}
。即在24
和16
之后添加填充。
我无法解释为什么拥有显式默认构造函数与具有隐式默认构造函数有何不同。
第二个问题
如果我随后将bar
的{{1}}更改为baz
,它将变回返回struct
。我也不能解释这一点。为什么访问修饰符会更改结构的大小?
答案 0 :(得分:24)
这全部归结为您的类型是否为聚合类型。与
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;
};
foo
和bar
现在是私有的(因为默认情况下类具有私有可访问性),这又意味着Base
不再是聚合,因为不允许聚合具有私有成员。这意味着我们回到第一种情况的工作方式。
答案 1 :(得分:5)
在您的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!
}
在向基类中添加显式构造函数或将struct
关键字更改为class
时,Derived
类不再满足POD定义,因此尾部填充重用不会发生。