基类的填充将被复制到派生类中

will the padding of base class be copied into the derived class?

本文关键字:派生 复制 填充 基类      更新时间:2023-10-16

最近,我一直在阅读"c++对象模型内部"。它说基类中使用的填充也应该复制到派生类中,以防您想将基类分配给派生类。因此,我在64位计算机上运行了一个测试:

class A {
public:
int valA;
char a;
};
class B : public A {
public:
char b;
};
class C : public B {
public:
char c;
};
int main(){
std::cout << sizeof(A) << " " << sizeof(B) << " " << sizeof(C) 
<< std::endl;
C c;
printf("%pn%pn%pn",&c,&c.b,&c.c);
}

结果如下:

8 12 12
0x7ffd22c5072c
0x7ffd22c50734
0x7ffd22c50735

那么为什么C和B的大小相同呢?尽管看起来B在A.中使用了3字节填充

那么为什么C的大小与B的大小相同呢?

因为B的尾部填充被重新用于C::b。可以重用填充,因为B不是POD(纯旧数据)类(因为它不是标准布局类)。

尽管看起来B在A.中使用了3字节填充

A的填充不能用于B的其他子对象,因为A是一个标准布局类,并且是可复制的,即A是POD类。


基类的填充会被复制到派生类中吗?

我想你不是想问复制的问题,而是想问派生类的基类子对象是否与单个类型具有相同的填充。

答案是,正如从上面可以推断的那样:填充将是相同的,除了尾部填充可以重新用于其他子对象,除非基类是POD,在这种情况下,它的填充不能重复使用。

在可以重用填充的情况下,标准没有指定是否重用,编译器之间也存在差异。


请解释或链接到";标准布局类型";。

当前标准草案:

[基本类型]

标量类型、标准布局类类型([class.prp])、此类类型的数组以及这些类型的cv限定版本统称为标准布局类型。


[class.prop](在标准的旧版本中,这些可以直接在[class]下找到)

一个类S是一个标准布局类,如果它:

  • (3.1)没有类型为非标准布局类(或此类类型的数组)或引用的非静态数据成员,

  • (3.2)没有虚拟功能,也没有虚拟基类,

  • (3.3)对所有非静态数据成员具有相同的访问控制,

  • (3.4)没有非标准布局基类,

  • (3.5)最多有一个任何给定类型的基类子对象

  • (3.6)具有类中的所有非静态数据成员和位字段,以及在同一类中首次声明的基类,并且

  • (3.7)没有类型的集合M(S)的元素作为基类,其中对于任何类型X,M(X)定义如下。107[注:M(X)为位于X中的零偏移--尾注]

    • (3.7.1)如果X是没有(可能继承的)非静态数据成员的非并集类类型,则集合M(X)为空。

    • (3.7.2)如果X是一个非并集类类型,其X0类型的非静态数据成员为零大小或是第一个X的非静态数据成员(其中所述成员可以是匿名的集合M(X)由X0和M(X0)的元素组成。

    • (3.7.3)如果X是并集类型,则集合M(X)是所有M(Ui)和包含所有Ui的集合的并集,其中每个Ui是X.的第i个非静态数据成员

    • (3.7.4)如果X是元素类型为Xe的数组类型,则集合M(X)由Xe和M(Xe)的元素组成。

    • (3.7.5)如果X是非类、非数组类型,则集合M(X)为空。

项目(3.6)适用于这种情况。B的一些成员不是首先在B中声明的。特别是,B::A::valA和B::A::A在A中首先声明。描述规则的一种更友好的方式是:类不能有直接成员,或者它的祖先都不能有成员。在这种情况下,基类和派生类都有成员,因此它不是标准布局。

CB大小相同,因为在您的平台上,ABI选择使用B中的填充来存储1字节成员C::c。由于整个B对象具有对齐4(由于A中的int成员),所以B在末尾具有3个字节的填充。

然而,BA的大小不同,因为在这种情况下,即使有空间,ABI显然也不允许将B::b存储在A的填充中。当所有A成员都是公共成员时,就会发生这种情况,就像您的示例中一样:如果您将任何成员设为私有成员,则ABC的大小都将为8。我相信这可能是为了ABI的向后兼容性,而不是受标准中任何语言的驱动。

我不知道标准中是否有直接允许的语言(但不需要),但在继承的情况下,这种类型的填充重用似乎是可以考虑的。例如,std::memcpy的文档显示:

如果对象可能重叠或不可TriviallyCopy,则不会指定memcpy的行为,并且可能未定义。

它继续定义潜在的重叠:

如果子对象是,则它可能重叠

  • 基类子对象,或
  • 用[[no_unique_address]]属性声明的非静态数据成员

第二个条件仅适用于C++20。

这似乎是为了允许共享填充:如果该子句不存在,则指向CB子类的指针上的memcpy将覆盖C::c的值,该值存储在通常用于B的填充中。